From 490c4de155a9baa7deace14e1ec7c38e65b2fa04 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:04:19 +0000 Subject: [PATCH 01/17] wip: add new URL types and supporting classes --- runtime/runtime-core/api/runtime-core.api | 211 ++++++++++++++ .../runtime/net/newnet/QueryParameters.kt | 96 +++++++ .../smithy/kotlin/runtime/net/newnet/Url.kt | 264 ++++++++++++++++++ .../kotlin/runtime/net/newnet/UrlPath.kt | 133 +++++++++ .../kotlin/runtime/net/newnet/UserInfo.kt | 100 +++++++ .../util/newcoll/MutableIteratorView.kt | 30 ++ .../util/newcoll/MutableListIteratorView.kt | 45 +++ .../runtime/util/newcoll/MutableListView.kt | 77 +++++ .../kotlin/runtime/util/text/Scanner.kt | 153 ++++++++++ .../runtime/util/text/encoding/Encodable.kt | 41 +++ .../runtime/util/text/encoding/Encoding.kt | 129 +++++++++ .../kotlin/runtime/net/newnet/UrlTest.kt | 116 ++++++++ .../util/text/encoding/EncodingTest.kt | 24 ++ 13 files changed, 1419 insertions(+) create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Scanner.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt create mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/newnet/UrlTest.kt create mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/encoding/EncodingTest.kt diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index 9d5dfadc3..c961324ec 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -795,6 +795,185 @@ public final class aws/smithy/kotlin/runtime/net/UserInfo { public fun toString ()Ljava/lang/String; } +public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/QueryParameters$Companion; + public synthetic fun (Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun clear ()V + public fun compute (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; + public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; + public fun computeIfAbsent (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/function/Function;)Ljava/util/List; + public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; + public fun computeIfPresent (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; + public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; + public fun containsKey (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Z + public final fun containsKey (Ljava/lang/Object;)Z + public final fun containsValue (Ljava/lang/Object;)Z + public fun containsValue (Ljava/util/List;)Z + public final fun entrySet ()Ljava/util/Set; + public fun get (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Ljava/util/List; + public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; + public final fun get (Ljava/lang/Object;)Ljava/util/List; + public fun getEntries ()Ljava/util/Set; + public fun getKeys ()Ljava/util/Set; + public fun getSize ()I + public fun getValues ()Ljava/util/Collection; + public fun isEmpty ()Z + public final fun keySet ()Ljava/util/Set; + public fun merge (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;Ljava/util/function/BiFunction;)Ljava/util/List; + public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; + public fun put (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public fun putAll (Ljava/util/Map;)V + public fun putIfAbsent (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public fun remove (Ljava/lang/Object;)Ljava/util/List; + public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z + public fun replace (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public fun replace (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;Ljava/util/List;)Z + public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z + public fun replaceAll (Ljava/util/function/BiFunction;)V + public final fun size ()I + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder; + public fun toString ()Ljava/lang/String; + public final fun values ()Ljava/util/Collection; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { + public fun ()V + public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; + public fun clear ()V + public fun containsKey (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Z + public final fun containsKey (Ljava/lang/Object;)Z + public final fun containsValue (Ljava/lang/Object;)Z + public fun containsValue (Ljava/util/List;)Z + public final fun entrySet ()Ljava/util/Set; + public fun get (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Ljava/util/List; + public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; + public final fun get (Ljava/lang/Object;)Ljava/util/List; + public fun getEntries ()Ljava/util/Set; + public fun getKeys ()Ljava/util/Set; + public fun getSize ()I + public fun getValues ()Ljava/util/Collection; + public fun isEmpty ()Z + public final fun keySet ()Ljava/util/Set; + public fun put (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public fun putAll (Ljava/util/Map;)V + public fun remove (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Ljava/util/List; + public final synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public final fun remove (Ljava/lang/Object;)Ljava/util/List; + public final fun size ()I + public final fun values ()Ljava/util/Collection; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; + public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; + public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/Url { + public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/Url$Companion; + public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILaws/smithy/kotlin/runtime/net/newnet/UrlPath;Laws/smithy/kotlin/runtime/net/newnet/QueryParameters;Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Laws/smithy/kotlin/runtime/net/newnet/UserInfo;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getFragment ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; + public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; + public final fun getPath ()Laws/smithy/kotlin/runtime/net/newnet/UrlPath; + public final fun getPort ()I + public final fun getQueryParameters ()Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; + public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; + public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/newnet/UserInfo; + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/Url$Builder; + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/Url$Builder { + public fun ()V + public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/Url; + public final fun clearQueryParameters ()V + public final fun clearUserInfo ()V + public final fun getFragmentDecoded ()Ljava/lang/String; + public final fun getFragmentEncoded ()Ljava/lang/String; + public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; + public final fun getPathDecoded ()Ljava/lang/String; + public final fun getPathEncoded ()Ljava/lang/String; + public final fun getPort ()Ljava/lang/Integer; + public final fun getQueryParametersDecoded ()Ljava/lang/String; + public final fun getQueryParametersEncoded ()Ljava/lang/String; + public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; + public final fun path (Lkotlin/jvm/functions/Function1;)V + public final fun queryParameters (Lkotlin/jvm/functions/Function1;)V + public final fun setFragmentDecoded (Ljava/lang/String;)V + public final fun setFragmentEncoded (Ljava/lang/String;)V + public final fun setHost (Laws/smithy/kotlin/runtime/net/Host;)V + public final fun setPathDecoded (Ljava/lang/String;)V + public final fun setPathEncoded (Ljava/lang/String;)V + public final fun setPort (Ljava/lang/Integer;)V + public final fun setQueryParametersDecoded (Ljava/lang/String;)V + public final fun setQueryParametersEncoded (Ljava/lang/String;)V + public final fun setScheme (Laws/smithy/kotlin/runtime/net/Scheme;)V + public final fun userInfo (Lkotlin/jvm/functions/Function1;)V +} + +public final class aws/smithy/kotlin/runtime/net/newnet/Url$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/Url; + public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/Url; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/UrlPath { + public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/UrlPath$Companion; + public synthetic fun (Ljava/util/List;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getSegments ()Ljava/util/List; + public final fun getTrailingSlash ()Z + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/UrlPath$Builder; + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/UrlPath$Builder { + public fun ()V + public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/UrlPath; + public final fun clearSegments ()V + public final fun getDecodedSegments ()Ljava/util/List; + public final fun getEncodedSegments ()Ljava/util/List; + public final fun getTrailingSlash ()Z + public final fun setTrailingSlash (Z)V +} + +public final class aws/smithy/kotlin/runtime/net/newnet/UrlPath$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/UrlPath; + public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UrlPath; + public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UrlPath; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/UserInfo { + public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/UserInfo$Companion; + public synthetic fun (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getPassword ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; + public final fun getUserName ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/UserInfo$Builder; + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/net/newnet/UserInfo$Builder { + public fun ()V + public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/UserInfo; + public final fun getPasswordDecoded ()Ljava/lang/String; + public final fun getPasswordEncoded ()Ljava/lang/String; + public final fun getUserNameDecoded ()Ljava/lang/String; + public final fun getUserNameEncoded ()Ljava/lang/String; + public final fun setPasswordDecoded (Ljava/lang/String;)V + public final fun setPasswordEncoded (Ljava/lang/String;)V + public final fun setUserNameDecoded (Ljava/lang/String;)V + public final fun setUserNameEncoded (Ljava/lang/String;)V +} + +public final class aws/smithy/kotlin/runtime/net/newnet/UserInfo$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/UserInfo; + public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UserInfo; + public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UserInfo; +} + public final class aws/smithy/kotlin/runtime/operation/ExecutionContext : aws/smithy/kotlin/runtime/util/MutableAttributes, kotlinx/coroutines/CoroutineScope { public static final field Companion Laws/smithy/kotlin/runtime/operation/ExecutionContext$Companion; public fun ()V @@ -1441,9 +1620,41 @@ public final class aws/smithy/kotlin/runtime/util/ValuesMap$DefaultImpls { public final class aws/smithy/kotlin/runtime/util/ValuesMapKt { } +public final class aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView : java/util/ListIterator, kotlin/jvm/internal/markers/KMutableListIterator { + public fun (Ljava/util/ListIterator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public fun add (Ljava/lang/Object;)V + public fun hasNext ()Z + public fun hasPrevious ()Z + public fun next ()Ljava/lang/Object; + public fun nextIndex ()I + public fun previous ()Ljava/lang/Object; + public fun previousIndex ()I + public fun remove ()V + public fun set (Ljava/lang/Object;)V +} + public final class aws/smithy/kotlin/runtime/util/text/TextKt { } public final class aws/smithy/kotlin/runtime/util/text/Utf8Kt { } +public final class aws/smithy/kotlin/runtime/util/text/encoding/Encodable { + public static final field Companion Laws/smithy/kotlin/runtime/util/text/encoding/Encodable$Companion; + public fun equals (Ljava/lang/Object;)Z + public final fun getDecoded ()Ljava/lang/String; + public final fun getEncoded ()Ljava/lang/String; + public final fun getEncoding ()Laws/smithy/kotlin/runtime/util/text/encoding/Encoding; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/util/text/encoding/Encodable$Companion { + public final fun getEmpty ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; +} + +public final class aws/smithy/kotlin/runtime/util/text/encoding/Encoding$DefaultImpls { + public static fun encodableFromDecoded (Laws/smithy/kotlin/runtime/util/text/encoding/Encoding;Ljava/lang/String;)Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; + public static fun encodableFromEncoded (Laws/smithy/kotlin/runtime/util/text/encoding/Encoding;Ljava/lang/String;)Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; +} + diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt new file mode 100644 index 000000000..f8e8327e3 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt @@ -0,0 +1,96 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.newnet + +import aws.smithy.kotlin.runtime.util.text.encoding.Encodable +import aws.smithy.kotlin.runtime.util.text.encoding.Encoding + +/** + * Represents the parameters in a URL query string. + */ +public class QueryParameters private constructor(private val delegate: Map>) : Map> by delegate { + public companion object { + /** + * Create new [QueryParameters] via a DSL builder block + * @param block The code to apply to the builder + * @return A new [QueryParameters] instance + */ + public operator fun invoke(block: Builder.() -> Unit): QueryParameters = Builder().apply(block).build() + + private fun asDecoded(delegate: Map>) = asString(delegate, Encodable::decoded) + private fun asEncoded(delegate: Map>) = asString(delegate, Encodable::encoded) + + private fun asString(delegate: Map>, encodableForm: (Encodable) -> String) = + delegate + .entries + .joinToString("&") { (key, values) -> + values.joinToString("&") { "${encodableForm(key)}=${encodableForm(it)}" } + } + + /** + * Parse a **decoded** query string into a [QueryParameters] instance + * @param decoded A decoded query string + * @return A new [QueryParameters] instance + */ + public fun parseDecoded(decoded: String): QueryParameters = QueryParameters { parseDecoded(decoded) } + + /** + * Parse an **encoded** query string into a [QueryParameters] instance + * @param encoded An encoded query string + * @return A new [QueryParameters] instance + */ + public fun parseEncoded(encoded: String): QueryParameters = QueryParameters { parseEncoded(encoded) } + } + + /** + * Copy the properties of this [QueryParameters] instance into a new [Builder] object. Any changes to the builder + * *will not* affect this instance. + */ + public fun toBuilder(): Builder = Builder(delegate.mapValuesTo(mutableMapOf()) { (_, v) -> v.toMutableList() }) + + override fun toString(): String = asEncoded(delegate) + + // TODO dsl functions for access to encoded/decoded param maps, likely requires some kind of MutableMapView similar + // to MutableListView + /** + * A mutable builder used to construct [QueryParameters] instances + */ + public class Builder internal constructor( + private val delegate: MutableMap>, + ) : MutableMap> by delegate { + /** + * Initialize an empty [QueryParameters] builder + */ + public constructor() : this(mutableMapOf()) + + internal fun asDecoded() = asDecoded(delegate) + internal fun asEncoded() = asEncoded(delegate) + + internal fun parseDecoded(decoded: String) = parse(decoded, Encoding.Query::encodableFromDecoded) + internal fun parseEncoded(encoded: String) = parse(encoded, Encoding.Query::encodableFromEncoded) + + private fun parse(text: String, toEncodable: (String) -> Encodable) { + clear() + if (text.isNotEmpty()) { + text.split("&").forEach { segment -> + val parts = segment.split("=") + val key = parts[0] + val value = when (parts.size) { + 1 -> "" + 2 -> parts[1] + else -> throw IllegalArgumentException("invalid query string segment $segment") + } + getOrPut(toEncodable(key), ::mutableListOf).add(toEncodable(value)) + } + } + } + + /** + * Build a new [QueryParameters] from the currently-configured builder values + * @return A new [QueryParameters] instance + */ + public fun build(): QueryParameters = QueryParameters(delegate.mapValues { (_, v) -> v.toList() }.toMap()) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt new file mode 100644 index 000000000..1cdf484f1 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt @@ -0,0 +1,264 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.newnet + +import aws.smithy.kotlin.runtime.net.Host +import aws.smithy.kotlin.runtime.net.Scheme +import aws.smithy.kotlin.runtime.net.splitHostPort +import aws.smithy.kotlin.runtime.net.toUrlString +import aws.smithy.kotlin.runtime.util.text.Scanner +import aws.smithy.kotlin.runtime.util.text.encoding.Encodable +import aws.smithy.kotlin.runtime.util.text.encoding.Encoding + +/** + * Represents a full, valid URL + * @param scheme The wire protocol (e.g., http, https, etc.) + * @param host The [Host] for the URL + * @param port The remote port number for the URL (e.g., TCP port) + * @param path The path element of this URL + * @param queryParameters Optional query parameters for this URL. Note that `null` parameters are different from *empty* + * parameters. + * @param fragment Optional fragment component of this URL (without the `#` character) + * @param userInfo Optional user authentication information for this URL + */ +public class Url private constructor( + public val scheme: Scheme, + public val host: Host, + public val port: Int, + public val path: UrlPath, + public val queryParameters: QueryParameters?, + public val fragment: Encodable?, + public val userInfo: UserInfo?, +) { + public companion object { + /** + * Create a new [Url] via a DSL builder block + * @param block The code to apply to the builder + * @return A new [Url] instance + */ + public operator fun invoke(block: Builder.() -> Unit): Url = Builder().apply(block).build() + + /** + * Parse an **encoded** string into a [Url] instance + * @param encoded An encoded URL string + * @return A new [Url] instance + */ + public fun parseEncoded(encoded: String): Url = Url { + val scanner = Scanner(encoded) + scanner.requireAndSkip("://") { scheme = Scheme.parse(it) } + + scanner.optionalAndSkip("@") { + userInfo { parseEncoded(it) } + } + + scanner.upToOrEnd("/", "?", "#") { authority -> + val (h, p) = authority.splitHostPort() + host = h + p?.let { port = it } + } + + scanner.ifStartsWith("/") { + scanner.upToOrEnd("?", "#") { + path { parseEncoded("/$it") } + } + } + + scanner.ifStartsWith("?") { + scanner.upToOrEnd("#") { + queryParameters { parseEncoded(it) } + } + } + + scanner.ifStartsWith("#") { + scanner.upToOrEnd { fragmentEncoded = it } + } + } + } + + init { + require(port in 1..65535) { "Given port $port is not in required range [1, 65535]" } + } + + /** + * Copy the properties of this [Url] instance into a new [Builder] object. Any changes to the builder *will not* + * affect this instance. + */ + public fun toBuilder(): Builder = Builder(this) + + /** + * Returns the encoded string representation of this URL + */ + override fun toString(): String = buildString { + append(scheme.protocolName) + append("://") + append(host.toUrlString()) + if (port != scheme.defaultPort) { + append(":") + append(port) + } + append(path) + queryParameters?.let { + append('?') + append(it) + } + fragment?.let { + append('#') + append(it.encoded) + } + } + + /** + * A mutable builder used to construct [Url] instances + */ + public class Builder internal constructor(url: Url?) { + /** + * Initialize an empty [Url] builder + */ + public constructor() : this(null) + + // Simple fields + + /** + * The wire protocol (e.g., http, https, etc.) + */ + public var scheme: Scheme = url?.scheme ?: Scheme.HTTPS + + /** + * The [Host] for the URL + */ + public var host: Host = url?.host ?: Host.Domain("") + + /** + * The remote port number for the URL (e.g., TCP port) + */ + public var port: Int? = url?.port + + // Path + + private val path: UrlPath.Builder = url?.path?.toBuilder() ?: UrlPath.Builder() + + /** + * Update the [UrlPath] of this URL via a DSL builder block + * @param block The code to apply to the [UrlPath] builder + */ + public fun path(block: UrlPath.Builder.() -> Unit) { + path.apply(block) + } + + /** + * Get or set the URL path as a **decoded** string + */ + public var pathDecoded: String + get() = path.asDecoded() + set(value) { path.parseDecoded(value) } + + /** + * Get or set the URL path as an **encoded** string + */ + public var pathEncoded: String + get() = path.asEncoded() + set(value) { path.parseEncoded(value) } + + // Query parameters + + private var queryParameters = url?.queryParameters?.toBuilder() + + /** + * Remove all query parameters from this URL + */ + public fun clearQueryParameters() { + queryParameters = null + } + + /** + * Update the [QueryParameters] of this URL via a DSL builder block + * @param block The code to apply to the [QueryParameters] builder + */ + public fun queryParameters(block: QueryParameters.Builder.() -> Unit) { + val queryParameters = this.queryParameters ?: QueryParameters.Builder().also { this.queryParameters = it } + queryParameters.apply(block) + } + + /** + * Get or set the query parameters as a **decoded** string + */ + public var queryParametersDecoded: String? + get() = queryParameters?.asDecoded() + set(value) { + if (value == null) { + queryParameters = null + } else { + queryParameters { parseDecoded(value) } + } + } + + /** + * Get or set the query parameters as an **encoded** string + */ + public var queryParametersEncoded: String? + get() = queryParameters?.asEncoded() + set(value) { + if (value == null) { + queryParameters = null + } else { + queryParameters { parseEncoded(value) } + } + } + + // Fragment + + private var fragment: Encodable? = url?.fragment + + /** + * Get or set the fragment as a **decoded** string + */ + public var fragmentDecoded: String? + get() = fragment?.decoded + set(value) { fragment = value?.let(Encoding.Fragment::encodableFromDecoded) } + + /** + * Get or set the fragment as an **encoded** string + */ + public var fragmentEncoded: String? + get() = fragment?.encoded + set(value) { fragment = value?.let(Encoding.Fragment::encodableFromEncoded) } + + // User info + + private var userInfo: UserInfo.Builder? = url?.userInfo?.toBuilder() + + /** + * Remove the user info from the URL + */ + public fun clearUserInfo() { + userInfo = null + } + + /** + * Set the user info in this URL via a DSL builder block + * @param block The code to apply to the [UserInfo] builder + */ + public fun userInfo(block: UserInfo.Builder.() -> Unit) { + val userInfo = this.userInfo ?: UserInfo.Builder().also { this.userInfo = it } + userInfo.apply(block) + } + + // Build method + + /** + * Build a new [Url] from the currently-configured builder values + * @return A new [Url] instance + */ + public fun build(): Url = Url( + scheme, + host, + port ?: scheme.defaultPort, + path.build(), + queryParameters?.build(), + fragment, + userInfo?.build(), + ) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt new file mode 100644 index 000000000..1ec4c6acb --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.newnet + +import aws.smithy.kotlin.runtime.util.newcoll.MutableListView +import aws.smithy.kotlin.runtime.util.text.encoding.Encodable +import aws.smithy.kotlin.runtime.util.text.encoding.Encoding + +/** + * Represents the path component of a URL + * @param segments A list of path segments + * @param trailingSlash Indicates whether a trailing slash is present in the path (e.g., "/foo/bar/" vs "/foo/bar") + */ +public class UrlPath private constructor(public val segments: List, public val trailingSlash: Boolean = false) { + public companion object { + /** + * Create a new [UrlPath] via a DSL builder block + * @param block The code to apply to the builder + * @return A new [UrlPath] instance + */ + public operator fun invoke(block: Builder.() -> Unit): UrlPath = Builder().apply(block).build() + + private fun asDecoded(segments: List, trailingSlash: Boolean) = + asString(segments, trailingSlash, Encodable::decoded) + + private fun asEncoded(segments: List, trailingSlash: Boolean) = + asString(segments, trailingSlash, Encodable::encoded) + + private fun asString(segments: List, trailingSlash: Boolean, encodableForm: (Encodable) -> String) = when { + segments.isEmpty() -> if (trailingSlash) "/" else "" + else -> segments.joinToString( + separator = "/", + prefix = "/", + postfix = if (trailingSlash) "/" else "", + transform = encodableForm, + ) + } + + /** + * Parse a **decoded** path string into a [UrlPath] instance + * @param decoded A decoded path string + * @return A new [UrlPath] instance + */ + public fun parseDecoded(decoded: String): UrlPath = UrlPath { parseDecoded(decoded) } + + /** + * Parse an **encoded** path string into a [UrlPath] instance + * @param encoded An encoded path string + * @return A new [UrlPath] instance + */ + public fun parseEncoded(encoded: String): UrlPath = UrlPath { parseEncoded(encoded) } + } + + /** + * Copy the properties of this [UrlPath] instance into a new [Builder] object. Any changes to the builder *will not* + * affect this instance. + */ + public fun toBuilder(): Builder = Builder(this) + + override fun toString(): String = asEncoded(segments, trailingSlash) + + /** + * A mutable builder used to construct [UrlPath] instances + */ + public class Builder internal constructor(path: UrlPath?) { + /** + * Initialize an empty [UrlPath] builder + */ + public constructor() : this(null) + + private val segments: MutableList = path?.segments?.toMutableList() ?: mutableListOf() + + /** + * Remove all existing segments + */ + public fun clearSegments() { + segments.clear() + } + + /** + * A mutable list of **decoded** path segments. Any changes to this list will update the builder. + */ + public val decodedSegments: MutableList = MutableListView( + segments, + Encodable::decoded, + Encoding.Query::encodableFromDecoded, + ) + + /** + * A mutable list of **encoded** path segments. Any changes to this list will update the builder. + */ + public val encodedSegments: MutableList = MutableListView( + segments, + Encodable::encoded, + Encoding.Query::encodableFromEncoded, + ) + + /** + * Indicates whether a trailing slash is present in the path (e.g., "/foo/bar/" vs "/foo/bar") + */ + public var trailingSlash: Boolean = path?.trailingSlash ?: false + + internal fun asDecoded() = asDecoded(segments, trailingSlash) + internal fun asEncoded() = asEncoded(segments, trailingSlash) + + internal fun parseDecoded(decoded: String): Unit = parse(decoded, Encoding.Path::encodableFromDecoded) + internal fun parseEncoded(encoded: String): Unit = parse(encoded, Encoding.Path::encodableFromEncoded) + + private fun parse(text: String, toEncodable: (String) -> Encodable) { + segments.clear() + + when (text) { + "" -> trailingSlash = false + "/" -> trailingSlash = true + else -> { + val noLeadingSlash = text.removePrefix("/") + trailingSlash = noLeadingSlash.endsWith('/') + + val trimmed = if (trailingSlash) noLeadingSlash.removeSuffix("/") else noLeadingSlash + trimmed.split('/').mapTo(segments, toEncodable) + } + } + } + + /** + * Build a new [UrlPath] from the currently-configured builder values + * @return A new [UrlPath] instance + */ + public fun build(): UrlPath = UrlPath(segments.toList(), trailingSlash) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt new file mode 100644 index 000000000..ea359cbf6 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.newnet + +import aws.smithy.kotlin.runtime.util.text.encoding.Encodable +import aws.smithy.kotlin.runtime.util.text.encoding.Encoding + +/** + * Represents the user authentication information in a URL + * @param userName The user name of the caller + * @param password The password for the caller + */ +public class UserInfo private constructor(public val userName: Encodable, public val password: Encodable) { + public companion object { + /** + * Create a new [UserInfo] via a DSL builder block + * @param block The code to apply to the builder + * @return A new [UserInfo] instance + */ + public operator fun invoke(block: Builder.() -> Unit): UserInfo = Builder().apply(block).build() + + /** + * Parse a **decoded** string into a [UserInfo] instance + * @param decoded A decoded user info string + * @return A new [UserInfo] instance + */ + public fun parseDecoded(decoded: String): UserInfo = UserInfo { parseDecoded(decoded) } + + /** + * Parse an **encoded** string into a [UserInfo] instance + * @param encoded An encoded user info string + * @return A new [UserInfo] instance + */ + public fun parseEncoded(encoded: String): UserInfo = UserInfo { parseEncoded(encoded) } + } + + /** + * Copy the properties of this [UserInfo] instance into a new [Builder] object. Any changes to the builder + * *will not* affect this instance. + */ + public fun toBuilder(): Builder = Builder(this) + + override fun toString(): String = "${userName.encoded}:${password.encoded}" + + /** + * A mutable builder used to construct [UserInfo] instances + */ + public class Builder internal constructor(userInfo: UserInfo?) { + /** + * Initialize an empty [UserInfo] builder + */ + public constructor() : this(null) + + private var userName = userInfo?.userName ?: Encodable.Empty + + public var userNameDecoded: String + get() = userName.decoded + set(value) { userName = Encoding.UserInfo.encodableFromDecoded(value) } + + public var userNameEncoded: String + get() = userName.encoded + set(value) { userName = Encoding.UserInfo.encodableFromEncoded(value) } + + private var password = userInfo?.password ?: Encodable.Empty + + public var passwordDecoded: String + get() = password.decoded + set(value) { password = Encoding.UserInfo.encodableFromDecoded(value) } + + public var passwordEncoded: String + get() = password.encoded + set(value) { password = Encoding.UserInfo.encodableFromEncoded(value) } + + internal fun parseDecoded(decoded: String) = parse(decoded, Encoding.UserInfo::encodableFromDecoded) + internal fun parseEncoded(encoded: String) = parse(encoded, Encoding.UserInfo::encodableFromEncoded) + + private fun parse(text: String, toEncodable: (String) -> Encodable) { + if (text.isEmpty()) { + userName = Encodable.Empty + password = Encodable.Empty + } else { + val parts = text.split(":", limit = 2) + userName = toEncodable(parts[0]) + password = when (parts.size) { + 1 -> Encodable.Empty + 2 -> toEncodable(parts[1]) + else -> throw IllegalArgumentException("invalid user info string $text") + } + } + } + + /** + * Build a new [UserInfo] from the currently-configured builder values + * @return A new [UserInfo] instance + */ + public fun build(): UserInfo = UserInfo(userName, password) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt new file mode 100644 index 000000000..ed0817516 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util.newcoll + +import aws.smithy.kotlin.runtime.InternalApi + +/** + * A mutable view of a mutable iterator. This class presents the elements of a source iterator in a different + * format/type. Updates to this iterator (i.e., advancing to the next element or removing the current element) are + * propagated to the source iterator. + * @param Src The type of elements in the source iterator + * @param Dest The type of elements in this view + * @param srcIterator The source iterator containing the canonical elements + * @param srcToDest A function that transforms a [Src] object to a [Dest] + */ +@InternalApi +public class MutableIteratorView( + private val srcIterator: MutableIterator, + private val srcToDest: (Src) -> Dest, +) : MutableIterator { + override fun hasNext(): Boolean = srcIterator.hasNext() + + override fun next(): Dest = srcToDest(srcIterator.next()) + + override fun remove() { + srcIterator.remove() + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt new file mode 100644 index 000000000..36acf7c41 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util.newcoll + +/** + * A mutable view of a mutable list iterator. This class presents the elements of a source iterator in a different + * format/type. Updates to this iterator (e.g., advancing to the next element, additions, removals, etc.) are propagated + * to the source iterator. + * @param Src The type of elements in the source iterator + * @param Dest The type of elements in this view + * @param srcIterator The source iterator containing the canonical elements + * @param srcToDest A function that transforms a [Src] object to a [Dest] + * @param destToSrc A function that transforms a [Dest] object to a [Src] + */ +public class MutableListIteratorView( + private val srcIterator: MutableListIterator, + private val srcToDest: (Src) -> Dest, + private val destToSrc: (Dest) -> Src, +) : MutableListIterator { + override fun add(element: Dest) { + srcIterator.add(destToSrc(element)) + } + + override fun hasNext(): Boolean = srcIterator.hasNext() + + override fun hasPrevious(): Boolean = srcIterator.hasPrevious() + + override fun next(): Dest = srcToDest(srcIterator.next()) + + override fun nextIndex(): Int = srcIterator.nextIndex() + + override fun previous(): Dest = srcToDest(srcIterator.previous()) + + override fun previousIndex(): Int = srcIterator.previousIndex() + + override fun remove() { + srcIterator.remove() + } + + override fun set(element: Dest) { + srcIterator.set(destToSrc(element)) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt new file mode 100644 index 000000000..fdcf22ffb --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util.newcoll + +import aws.smithy.kotlin.runtime.InternalApi + +/** + * A mutable view of a mutable list. This class presents the elements of a source list in a different format/type. + * Updates to this view (e.g., additions, removals, etc.) are propagated to the source list. + * @param Src The type of elements in the source list + * @param Dest The type of elements in this view + * @param srcList The source list containing the canonical elements + * @param srcToDest A function that transforms a [Src] object to a [Dest] + * @param destToSrc A function that transforms a [Dest] object to a [Src] + */ +@InternalApi +public class MutableListView( + private val srcList: MutableList, + private val srcToDest: (Src) -> Dest, + private val destToSrc: (Dest) -> Src, +) : MutableList { + override fun add(element: Dest): Boolean = srcList.add(destToSrc(element)) + + override fun add(index: Int, element: Dest) { + srcList.add(index, destToSrc(element)) + } + + override fun addAll(elements: Collection): Boolean = srcList.addAll(elements.map(destToSrc)) + + override fun addAll(index: Int, elements: Collection): Boolean = + srcList.addAll(index, elements.map(destToSrc)) + + override fun clear() { + srcList.clear() + } + + override fun contains(element: Dest): Boolean = srcList.contains(destToSrc(element)) + + override fun containsAll(elements: Collection): Boolean = srcList.containsAll(elements.map(destToSrc)) + + override fun get(index: Int): Dest = srcToDest(srcList[index]) + + override fun indexOf(element: Dest): Int = srcList.indexOf(destToSrc(element)) + + override fun isEmpty(): Boolean = srcList.isEmpty() + + override fun iterator(): MutableIterator = MutableIteratorView(srcList.iterator(), srcToDest) + + override fun lastIndexOf(element: Dest): Int = srcList.lastIndexOf(destToSrc(element)) + + override fun listIterator(): MutableListIterator = + MutableListIteratorView(srcList.listIterator(), srcToDest, destToSrc) + + override fun listIterator(index: Int): MutableListIterator = + MutableListIteratorView(srcList.listIterator(index), srcToDest, destToSrc) + + override fun remove(element: Dest): Boolean = srcList.remove(destToSrc(element)) + + override fun removeAll(elements: Collection): Boolean = srcList.removeAll(elements.map(destToSrc)) + + override fun removeAt(index: Int): Dest = srcToDest(srcList.removeAt(index)) + + override fun retainAll(elements: Collection): Boolean = srcList.retainAll(elements.map(destToSrc)) + + override fun set(index: Int, element: Dest): Dest = srcToDest(srcList.set(index, destToSrc(element))) + + override val size: Int + get() = srcList.size + + override fun subList(fromIndex: Int, toIndex: Int): MutableList = MutableListView( + srcList.subList(fromIndex, toIndex), + srcToDest, + destToSrc, + ) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Scanner.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Scanner.kt new file mode 100644 index 000000000..f56276a09 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Scanner.kt @@ -0,0 +1,153 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util.text + +import aws.smithy.kotlin.runtime.InternalApi + +/** + * A stateful scanner for processing a string in pieces (e.g., during parsing). This class operates on a single input + * [text] and tracks a current position (starting at the beginning). Various operations move or may move the position + * forward but never backwards. + * @param text The string to process + */ +@InternalApi +public class Scanner(public val text: String) { + private var currentIndex = 0 + + private fun findAnyOf(literals: Array): Pair? = literals + .map { it to text.indexOf(it, startIndex = currentIndex) } + .filter { (_, index) -> index != -1 } + .minByOrNull { (_, index) -> index } + + /** + * If [text] starts with [prefix] at the current position, advances the current position by the length of [prefix] + * and invokes the given [handler]. Otherwise, the position does not change and the [handler] is not invoked. + * + * **Example**: + * + * ```kotlin + * val scanner = Scanner("abc def") + * scanner.ifStartsWith("abc") { + * // Scanner has now skipped "abc" + * println(scanner) // Prints "Scanner(' def')" + * } + * ``` + * + * @param prefix The prefix to test for at the current position + * @param handler The handler to invoke if [text] starts with [prefix] at the current position + */ + public fun ifStartsWith(prefix: String, handler: () -> Unit) { + if (startsWith(prefix)) { + currentIndex += prefix.length + handler() + } + } + + /** + * If any of [literals] is found at/after the current position, invokes the given [handler] on the substring *up to + * but not including* the literal and then advances the current position *past* the literal. If multiple of the + * given [literals] are found, the nearest one is processed. If none of [literals] are found, the position does not + * change and the [handler] is not invoked. + * + * This method is similar to [requireAndSkip] except that no exception is thrown when none of [literals] are found. + * + * **Example**: + * + * ```kotlin + * val scanner = Scanner("ianbotsf@somewhere.net") + * scanner.optionalAndSkip("@") { username -> + * println(username) // Prints "ianbotsf" + * } + * // Scanner has now skipped "ianbotsf@" + * println(scanner) // Prints "Scanner('somewhere.net')" + * ``` + * + * @param literals One or more strings to search for at/after the current position + * @param handler The handler to invoke on the substring *up to but not including* the nearest found element of + * [literals]. If none of [literals] are found, this handler is not invoked. + */ + public fun optionalAndSkip(vararg literals: String, handler: (String) -> Unit) { + findAnyOf(literals)?.let { (literal, startIndex) -> + processAndSkip(literal, startIndex, handler) + } + } + + private fun process(untilIndex: Int, handler: (String) -> Unit) { + val captured = text.substring(currentIndex, untilIndex) + currentIndex = untilIndex + handler(captured) + } + + private fun processAndSkip(literal: String, startIndex: Int, handler: (String) -> Unit) { + process(startIndex, handler) + currentIndex += literal.length + } + + /** + * If any of [literals] is found at/after the current position, invokes the given [handler] on the substring *up to + * but not including* the literal and then advances the current position *past* the literal. If multiple of the + * given [literals] are found, the nearest one is processed. If none of [literals] is found, an + * [IllegalArgumentException] is thrown. + * + * This method is similar to [optionalAndSkip] except that an exception is thrown when none of [literals] are found. + * + * **Example**: + * + * ```kotlin + * val scanner = Scanner("ianbotsf@somewhere.net") + * scanner.requireAndSkip("@") { username -> + * println(username) // Prints "ianbotsf" + * } + * // Scanner has now skipped "ianbotsf@" + * println(scanner) // Prints "Scanner('somewhere.net')" + * ``` + * + * @param literals One or more strings to search for at/after the current position + * @param handler The handler to invoke on the substring *up to but not including* the nearest found element of + * [literals]. + */ + public fun requireAndSkip(vararg literals: String, handler: (String) -> Unit) { + val (literal, startIndex) = requireNotNull(findAnyOf(literals)) { "Cannot find any of ${literals.toList()}" } + processAndSkip(literal, startIndex, handler) + } + + override fun toString(): String = "Scanner(remainingText='${text.substring(currentIndex)}')" + + /** + * Determines if [text] starts with [prefix] at the current position. + * @param prefix The prefix to search for at the current position + * @return True if [text] at the current position starts with [prefix]; otherwise, false + */ + public fun startsWith(prefix: String): Boolean = text.regionMatches(currentIndex, prefix, 0, prefix.length) + + /** + * Invokes the given [handler] on the substring *up to but not including* the nearest found element of [literals] + * at/after the current position. If none of [literals] are found, invokes [handler] on the remainder of [text] from + * the current position to the end of the string. After [handler] is invoked, the current position is advanced past + * the found element of [literals] or past the end of the string if none of [literals] are found. + * + * **Example**: + * + * ```kotlin + * val scanner = Scanner("abc,def") + * scanner.upToOrEnd(",") { + * println(it) // Prints "abc" + * } + * // Scanner has now skipped "abc," + * scanner.upToOrEnd(",") { + * println(it) // Prints "def" + * } + * // Scanner has now skipped "def" + * ``` + * + * @param literals One or more strings to search for at/after the current position + * @param handler The handler to invoke on the substring *up to but not including nearest found element of + * [literals] or, if none of [literals] are found, + */ + public fun upToOrEnd(vararg literals: String, handler: (String) -> Unit) { + val untilIndex = findAnyOf(literals)?.second ?: text.length + process(untilIndex, handler) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt new file mode 100644 index 000000000..5a4e66197 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util.text.encoding + +public class Encodable internal constructor( + public val decoded: String, + public val encoded: String, + public val encoding: Encoding, +) { + public companion object { + public val Empty: Encodable = Encodable("", "", Encoding.None) + } + + override fun toString(): String = buildString { + append("Encodable(decoded=") + append(decoded) + append(",encoded=") + append(encoded) + append(",encoding=") + append(encoding.name) + append(")") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Encodable) return false + + if (decoded != other.decoded) return false + if (encoded != other.encoded) return false + return encoding == other.encoding + } + + override fun hashCode(): Int { + var result = decoded.hashCode() + result = 31 * result + encoded.hashCode() + result = 31 * result + encoding.hashCode() + return result + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt new file mode 100644 index 000000000..547cd190d --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt @@ -0,0 +1,129 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util.text.encoding + +import aws.smithy.kotlin.runtime.InternalApi + +private val ALPHA = (('A'..'Z') + ('a'..'z')).toSet() +private val DIGIT = ('0'..'9').toSet() +private val UNRESERVED = ALPHA + DIGIT + setOf('-', '.', '_', '~') +private val SUB_DELIMS = setOf('!', '$', '&', '\'', '(', ')', '*', ',', ';', '=') +private val VALID_UCHAR = UNRESERVED + SUB_DELIMS +private val VALID_PCHAR = VALID_UCHAR + setOf(':', '@') +private val VALID_FCHAR = VALID_PCHAR + setOf('/', '?') +private val VALID_QCHAR = VALID_FCHAR - setOf('&', '=') + +@InternalApi +public interface Encoding { + @InternalApi + public companion object { + public val UserInfo: Encoding = PercentEncoding("user info", VALID_UCHAR) + public val Path: Encoding = PercentEncoding("path", VALID_PCHAR) + public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR, mapOf(' ' to '+')) + public val Fragment: Encoding = PercentEncoding("fragment", VALID_FCHAR) + + internal val None = object : Encoding { + override val name = "(no encoding)" + override fun decode(encoded: String) = encoded + override fun encode(decoded: String) = decoded + } + } + + public val name: String + + public fun decode(encoded: String): String + public fun encode(decoded: String): String + + public fun encodableFromDecoded(decoded: String): Encodable = Encodable(decoded, encode(decoded), this) + public fun encodableFromEncoded(encoded: String): Encodable { + val decoded = decode(encoded) + val reencoded = encode(decoded) // TODO is this right? + return Encodable(decoded, reencoded, this) + } +} + +@InternalApi +public class PercentEncoding( + override val name: String, + public val validChars: Set, + public val specialMapping: Map = mapOf(), +) : Encoding { + @InternalApi + public companion object { + private const val UPPER_HEX = "0123456789ABCDEF" + + private fun percentAsciiEncode(char: Char) = buildString { + val value = char.code and 0xff + append('%') + append(UPPER_HEX[value shr 4]) + append(UPPER_HEX[value and 0x0f]) + } + + public fun StringBuilder.percentEncode(byte: Byte) { + val value = byte.toInt() and 0xff + append('%') + append(UPPER_HEX[value shr 4]) + append(UPPER_HEX[value and 0x0f]) + } + } + + @OptIn(ExperimentalStdlibApi::class) + private val asciiMapping = (0..<128) + .map(Int::toChar) + .filterNot(validChars::contains) + .associateWith(::percentAsciiEncode) + + private val encodeMap = asciiMapping + specialMapping.mapValues { (_, char) -> char.toString() } + + private val decodeMap = (validChars.associateWith { it } + specialMapping) + .entries + .associate { (decoded, encoded) -> encoded to decoded } + + override fun decode(encoded: String): String = buildString(encoded.length) { + var byteBuffer: ByteArray? = null // Do not initialize unless needed + + var i = 0 + var c: Char + while (i < encoded.length) { + c = encoded[i] + if (c == '%') { + if (byteBuffer == null) { + byteBuffer = ByteArray((encoded.length - i) / 3) // Max remaining percent-encoded bytes + } + + var byteCount = 0 + while ((i + 2) < encoded.length && c == '%') { + val byte = encoded.substring(i + 1, i + 3).toIntOrNull(radix = 16)?.toByte() ?: break + byteBuffer[byteCount++] = byte + + i += 3 + if (i < encoded.length) c = encoded[i] + } + + append(byteBuffer.decodeToString(endIndex = byteCount)) + + if (i != encoded.length && c == '%') { + append(c) + i++ + } + } else { + append(decodeMap[c] ?: throw IllegalArgumentException("unknown encoding")) + i++ + } + } + } + + override fun encode(decoded: String): String = buildString(decoded.length) { + val bytes = decoded.encodeToByteArray() + for (byte in bytes) { + val char = byte.toInt().toChar() + if (char in validChars) { + append(char) + } else { + encodeMap[char]?.let(::append) ?: percentEncode(byte) + } + } + } +} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/newnet/UrlTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/newnet/UrlTest.kt new file mode 100644 index 000000000..8ff321f20 --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/newnet/UrlTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.newnet + +import kotlin.test.Test +import kotlin.test.fail +import aws.smithy.kotlin.runtime.net.Url as OldUrl +import aws.smithy.kotlin.runtime.net.newnet.Url as NewUrl + +class UrlTest { + private fun testEquivalence(vararg urls: String) { + val errors = urls.mapNotNull { url -> + val old = OldUrl.parse(url).toString() + val new = NewUrl.parseEncoded(url).toString() + if (old == new) null else "Old: <$old>, New: <$new>" + } + if (errors.isNotEmpty()) { + val message = errors.joinToString("\n", "Found the following errors:\n") { " $it" } + fail(message) + } + } + + @Test + fun testEquivalenceOfBasicUrls() { + testEquivalence( + "http://amazon.com", + "https://amazon.com", + ) + } + + @Test + fun testEquivalenceOfPorts() { + testEquivalence( + "https://amazon.com:8443", + ) + } + + @Test + fun testEquivalenceOfPaths() { + testEquivalence( + // "https://amazon.com/", // Old parser strips path `/`...is bug? + "https://amazon.com//", + "https://amazon.com/foo", + "https://amazon.com/foo/bar", + "https://amazon.com/foo/bar/", + "https://amazon.com/foo//bar/", + "https://amazon.com/foo/bar//", + ) + } + + @Test + fun testEquivalenceOfQueryStrings() { + testEquivalence( + // "https://amazon.com?", // Old parser strips '?' with empty query params...is bug? + "https://amazon.com?foo=bar", + // "https://amazon.com?foo=bar&baz=qux", // Old parser sorts query by key...is bug? + "https://amazon.com?foo=f%F0%9F%98%81o", + // "https://amazon.com?foo=f+o%20o", // Old parser encodes qparam ' ' to %20 instead of '+'...is bug? + ) + } + + @Test + fun testEquivalenceOfFragments() { + testEquivalence( + // "https://amazon.com#", // Old parser ignores empty fragments...is bug? + "https://amazon.com#foo", + "https://amazon.com#f%F0%9F%98%81o", + ) + } + + @Test + fun testEquivalenceOfMixedPathsAndQueryStrings() { + testEquivalence( + // "https://amazon.com/?", // Old parser strips '?' with empty query params...is bug? + // "https://amazon.com/?bar=baz", // Old parser strips path `/`...is bug? + // "https://amazon.com/foo?", // Old parser strips '?' with empty query params...is bug? + "https://amazon.com/foo?bar=baz", + ) + } + + @Test + fun testEquivalenceOfMixedPathsAndFragments() { + testEquivalence( + // "https://amazon.com/#", // Old parser strips path `/` and ignores empty fragments...are bugs? + // "https://amazon.com/#bar", // Old parser strips path `/`...is bug? + // "https://amazon.com/foo#", // Old parser ignores empty fragments...is bug? + "https://amazon.com/foo#bar", + ) + } + + @Test + fun testEquivalenceOfMixedQueryStringsAndFragments() { + testEquivalence( + // "https://amazon.com?#", // Old parser strips '?' with empty query params and ignores empty fragments...are bugs? + // "https://amazon.com?#baz", // Old parser strips '?' with empty query params...is bug? + // "https://amazon.com?foo=bar#", // Old parser ignores empty fragments...is bug? + "https://amazon.com?foo=bar#baz", + ) + } + + @Test + fun testEquivalenceOfMixedPathsAndQueryStringsAndFragments() { + testEquivalence( + // "https://amazon.com/?#", + // "https://amazon.com/?#quux", + // "https://amazon.com/?baz=qux#", + // "https://amazon.com/?baz=qux#quux", + // "https://amazon.com/foo/bar?#", + // "https://amazon.com/foo/bar?#quux", + // "https://amazon.com/foo/bar?baz=qux#", + "https://amazon.com/foo/bar?baz=qux#quux", + ) + } +} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/encoding/EncodingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/encoding/EncodingTest.kt new file mode 100644 index 000000000..2859b3951 --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/encoding/EncodingTest.kt @@ -0,0 +1,24 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.util.text.encoding + +import kotlin.test.Test +import kotlin.test.assertEquals + +class EncodingTest { + @Test + fun testUnicodeDecoding() { + val encoded = "f%F0%9F%98%81o" + val decoded = Encoding.Query.decode(encoded) + assertEquals("f😁o", decoded) + } + + @Test + fun testUnicodeEncoding() { + val decoded = "f😁o" + val encoded = Encoding.Query.encode(decoded) + assertEquals("f%F0%9F%98%81o", encoded) + } +} From 954ada383e46184068961eddbb0ef4bd1619a746 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Tue, 31 Oct 2023 21:34:05 +0000 Subject: [PATCH 02/17] addressing PR feedback: better spacing in toString output; include unknown character in exception message --- .../aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt | 4 ++-- .../aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt index 5a4e66197..724c3fbe5 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt @@ -16,9 +16,9 @@ public class Encodable internal constructor( override fun toString(): String = buildString { append("Encodable(decoded=") append(decoded) - append(",encoded=") + append(", encoded=") append(encoded) - append(",encoding=") + append(", encoding=") append(encoding.name) append(")") } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt index 547cd190d..f41985b16 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt @@ -109,7 +109,7 @@ public class PercentEncoding( i++ } } else { - append(decodeMap[c] ?: throw IllegalArgumentException("unknown encoding")) + append(decodeMap[c] ?: throw IllegalArgumentException("unknown encoding, cannot decode character '$c'")) i++ } } From ff5f6c04bd5e23ad71a9e649a3f7051fe85eea94 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Thu, 9 Nov 2023 00:16:05 +0000 Subject: [PATCH 03/17] Refactor the util namespace --- .../kotlin/codegen/core/RuntimeTypes.kt | 15 +- .../protocol/HttpBindingProtocolGenerator.kt | 6 +- .../protocol/HttpStringValuesMapSerializer.kt | 4 +- .../serde/DeserializeStructGenerator.kt | 2 +- .../serde/SerializeStructGenerator.kt | 6 +- .../runtime/auth/awssigning/Canonicalizer.kt | 7 +- .../runtime/auth/awssigning/RequestMutator.kt | 2 +- .../auth/awssigning/SignatureCalculator.kt | 2 +- .../DefaultSignatureCalculatorTest.kt | 4 +- .../tests/SigningSuiteTestBaseJVM.kt | 2 +- .../eventstream/EventStreamSigning.kt | 2 +- .../awsprotocol/eventstream/Message.kt | 2 +- .../awsprotocol/eventstream/Prelude.kt | 2 +- .../eventstream/EventStreamSigningTest.kt | 2 +- .../engine/okhttp/StreamingRequestBodyTest.kt | 2 +- .../kotlin/runtime/http/test/DownloadTest.kt | 2 +- .../kotlin/runtime/http/test/UploadTest.kt | 2 +- .../runtime/http/test/suite/Downloads.kt | 2 +- .../kotlin/runtime/http/test/suite/Uploads.kt | 2 +- .../http/test/FileUploadDownloadTest.kt | 2 +- .../FlexibleChecksumsRequestInterceptor.kt | 6 +- .../FlexibleChecksumsResponseInterceptor.kt | 2 +- .../interceptors/Md5ChecksumInterceptor.kt | 2 +- ...FlexibleChecksumsRequestInterceptorTest.kt | 2 +- .../runtime/httptest/HttpTrafficParser.kt | 2 +- .../runtime/httptest/RecordingConnection.kt | 2 +- runtime/protocol/http/api/http.api | 12 +- .../kotlin/runtime/http/DeferredHeaders.kt | 6 +- .../aws/smithy/kotlin/runtime/http/Headers.kt | 6 +- .../smithy/kotlin/runtime/http/util/Text.kt | 4 +- .../kotlin/runtime/http/util/TextTest.kt | 2 +- runtime/runtime-core/api/runtime-core.api | 160 +++++++++--------- .../collections/MutableIteratorView.kt | 30 ++++ .../collections/MutableListIteratorView.kt | 48 ++++++ .../runtime/collections/MutableListView.kt | 77 +++++++++ .../{util => collections}/ValuesMap.kt | 3 +- .../src/aws/smithy/kotlin/runtime/net/Host.kt | 2 +- .../kotlin/runtime/net/QueryParameters.kt | 10 +- .../src/aws/smithy/kotlin/runtime/net/Url.kt | 6 +- .../smithy/kotlin/runtime/net/UrlParser.kt | 4 +- .../runtime/net/newnet/QueryParameters.kt | 8 +- .../smithy/kotlin/runtime/net/newnet/Url.kt | 10 +- .../kotlin/runtime/net/newnet/UrlPath.kt | 14 +- .../kotlin/runtime/net/newnet/UserInfo.kt | 18 +- .../kotlin/runtime/{util => }/text/Scanner.kt | 2 +- .../kotlin/runtime/{util => }/text/Text.kt | 2 +- .../kotlin/runtime/{util => }/text/Utf8.kt | 2 +- .../runtime/{util => text/encoding}/Base64.kt | 2 +- .../{util => }/text/encoding/Encodable.kt | 2 +- .../kotlin/runtime/text/encoding/Encoding.kt | 31 ++++ .../runtime/{util => text/encoding}/Hex.kt | 2 +- .../encoding/PercentEncoding.kt} | 56 ++---- .../{util => collections}/ValuesMapTest.kt | 2 +- .../kotlin/runtime/hashing/Crc32Test.kt | 2 +- .../kotlin/runtime/hashing/Crc32cTest.kt | 2 +- .../smithy/kotlin/runtime/hashing/HmacTest.kt | 2 +- .../smithy/kotlin/runtime/hashing/Md5Test.kt | 2 +- .../smithy/kotlin/runtime/hashing/Sha1Test.kt | 2 +- .../kotlin/runtime/hashing/Sha256Test.kt | 2 +- .../runtime/{util => }/text/TextTest.kt | 2 +- .../runtime/{util => }/text/Utf8Test.kt | 2 +- .../{util => text/encoding}/Base64Test.kt | 2 +- .../{util => text/encoding}/HexTest.kt | 3 +- .../encoding/PercentEncodingTest.kt} | 8 +- .../serde/formurl/FormUrlSerializer.kt | 2 +- .../serde/xml/deserialization/XmlLexer.kt | 2 +- runtime/smithy-client/api/smithy-client.api | 12 +- .../runtime/client/endpoints/Endpoint.kt | 2 +- .../client/endpoints/functions/Functions.kt | 4 +- .../runtime/smithy/test/HttpRequestTest.kt | 2 +- 70 files changed, 422 insertions(+), 238 deletions(-) create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => collections}/ValuesMap.kt (98%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => }/text/Scanner.kt (99%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => }/text/Text.kt (99%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => }/text/Utf8.kt (97%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => text/encoding}/Base64.kt (99%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => }/text/encoding/Encodable.kt (95%) create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => text/encoding}/Hex.kt (96%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util/text/encoding/Encoding.kt => text/encoding/PercentEncoding.kt} (71%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => collections}/ValuesMapTest.kt (98%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => }/text/TextTest.kt (99%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => }/text/Utf8Test.kt (97%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => text/encoding}/Base64Test.kt (98%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => text/encoding}/HexTest.kt (96%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util/text/encoding/EncodingTest.kt => text/encoding/PercentEncodingTest.kt} (68%) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index b651041b3..9a1bfd51b 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -143,15 +143,21 @@ object RuntimeTypes { val SdkManagedGroup = symbol("SdkManagedGroup") val addIfManaged = symbol("addIfManaged", isExtension = true) } + + object Text : RuntimeTypePackage(KotlinDependency.CORE, "text") { + object Encoding : RuntimeTypePackage(KotlinDependency.CORE, "text.encoding") { + val decodeBase64 = symbol("decodeBase64") + val decodeBase64Bytes = symbol("decodeBase64Bytes") + val encodeBase64 = symbol("encodeBase64") + val encodeBase64String = symbol("encodeBase64String") + } + } + object Utils : RuntimeTypePackage(KotlinDependency.CORE, "util") { val Attributes = symbol("Attributes") val MutableAttributes = symbol("MutableAttributes") val attributesOf = symbol("attributesOf") val AttributeKey = symbol("AttributeKey") - val decodeBase64 = symbol("decodeBase64") - val decodeBase64Bytes = symbol("decodeBase64Bytes") - val encodeBase64 = symbol("encodeBase64") - val encodeBase64String = symbol("encodeBase64String") val ExpiringValue = symbol("ExpiringValue") val flattenIfPossible = symbol("flattenIfPossible") val get = symbol("get") @@ -161,7 +167,6 @@ object RuntimeTypes { val putIfAbsentNotNull = symbol("putIfAbsentNotNull") val ReadThroughCache = symbol("ReadThroughCache") val truthiness = symbol("truthiness") - val urlEncodeComponent = symbol("urlEncodeComponent", "text") val toNumber = symbol("toNumber") val type = symbol("type") } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index 4a671bfb1..53b4c7478 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -753,7 +753,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { ) } is BlobShape -> { - writer.write("builder.#L = response.headers[#S]?.#T()", memberName, headerName, RuntimeTypes.Core.Utils.decodeBase64) + writer.write("builder.#L = response.headers[#S]?.#T()", memberName, headerName, RuntimeTypes.Core.Text.Encoding.decodeBase64) } is StringShape -> { when { @@ -768,7 +768,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { ) } memberTarget.hasTrait() -> { - writer.write("builder.#L = response.headers[#S]?.#T()", memberName, headerName, RuntimeTypes.Core.Utils.decodeBase64) + writer.write("builder.#L = response.headers[#S]?.#T()", memberName, headerName, RuntimeTypes.Core.Text.Encoding.decodeBase64) } else -> { writer.write("builder.#L = response.headers[#S]", memberName, headerName) @@ -828,7 +828,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { "${enumSymbol.name}.fromValue(it)" } collectionMemberTarget.hasTrait() -> { - writer.addImport(RuntimeTypes.Core.Utils.decodeBase64) + writer.addImport(RuntimeTypes.Core.Text.Encoding.decodeBase64) "it.decodeBase64()" } else -> "" diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt index 8937fe616..b9793c3be 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt @@ -73,7 +73,7 @@ class HttpStringValuesMapSerializer( "append(#S, input.#L.#T()", paramName, memberName, - RuntimeTypes.Core.Utils.encodeBase64String, + RuntimeTypes.Core.Text.Encoding.encodeBase64String, ) writer.writeWithCondIfCheck(memberSymbol.isNullable, "input.$memberName?.isNotEmpty() == true", appendFn) } @@ -190,7 +190,7 @@ class HttpStringValuesMapSerializer( ".value" } memberTarget.hasTrait() -> { - writer.addImport(RuntimeTypes.Core.Utils.encodeBase64) + writer.addImport(RuntimeTypes.Core.Text.Encoding.encodeBase64) ".encodeBase64()" } else -> "" diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/DeserializeStructGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/DeserializeStructGenerator.kt index 23e8b3a91..5680af9f5 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/DeserializeStructGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/DeserializeStructGenerator.kt @@ -565,7 +565,7 @@ open class DeserializeStructGenerator( target.type == ShapeType.DOCUMENT -> "deserializeDocument()" target.type == ShapeType.BLOB -> { - writer.addImport(RuntimeTypes.Core.Utils.decodeBase64Bytes) + writer.addImport(RuntimeTypes.Core.Text.Encoding.decodeBase64Bytes) "deserializeString().decodeBase64Bytes()" } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/SerializeStructGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/SerializeStructGenerator.kt index 559d072cb..017f612bd 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/SerializeStructGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/serde/SerializeStructGenerator.kt @@ -438,7 +438,7 @@ open class SerializeStructGenerator( writer.write( "$containerName$listMemberName.forEach { ($keyName, $valueName) -> entry($keyName, $valueName.#T()) }", - RuntimeTypes.Core.Utils.encodeBase64String, + RuntimeTypes.Core.Text.Encoding.encodeBase64String, ) } @@ -517,7 +517,7 @@ open class SerializeStructGenerator( val containerName = if (nestingLevel == 0) "input." else "" writer.withBlock("for ($elementName in $containerName$listMemberName) {", "}") { - writer.write("serializeString($elementName.#T())", RuntimeTypes.Core.Utils.encodeBase64String) + writer.write("serializeString($elementName.#T())", RuntimeTypes.Core.Text.Encoding.encodeBase64String) } } @@ -625,7 +625,7 @@ open class SerializeStructGenerator( val target = member.targetOrSelf(ctx.model) val encoded = when { - target.type == ShapeType.BLOB -> writer.format("#L.#T()", identifier, RuntimeTypes.Core.Utils.encodeBase64String) + target.type == ShapeType.BLOB -> writer.format("#L.#T()", identifier, RuntimeTypes.Core.Text.Encoding.encodeBase64String) target.type == ShapeType.TIMESTAMP -> { writer.addImport(RuntimeTypes.Core.TimestampFormat) val tsFormat = member diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt index 3da76f3fa..58e58c8c1 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt @@ -15,9 +15,12 @@ import aws.smithy.kotlin.runtime.http.util.encodeLabel import aws.smithy.kotlin.runtime.io.* import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers import aws.smithy.kotlin.runtime.net.UrlBuilder +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex +import aws.smithy.kotlin.runtime.text.normalizePathSegments +import aws.smithy.kotlin.runtime.text.transformPathSegments +import aws.smithy.kotlin.runtime.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.text.urlReencodeComponent import aws.smithy.kotlin.runtime.time.TimestampFormat -import aws.smithy.kotlin.runtime.util.* -import aws.smithy.kotlin.runtime.util.text.* import kotlinx.coroutines.withContext /** diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt index d2e023361..0930d69a2 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.http.request.HttpRequest -import aws.smithy.kotlin.runtime.util.text.urlReencodeComponent +import aws.smithy.kotlin.runtime.text.urlReencodeComponent /** * An object that can mutate requests to include signing attributes. diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/SignatureCalculator.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/SignatureCalculator.kt index 14c978890..fe27320ff 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/SignatureCalculator.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/SignatureCalculator.kt @@ -5,10 +5,10 @@ package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.hashing.* +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.TimestampFormat import aws.smithy.kotlin.runtime.time.epochMilliseconds -import aws.smithy.kotlin.runtime.util.encodeToHex /** * An object that can calculate signatures based on canonical requests. diff --git a/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultSignatureCalculatorTest.kt b/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultSignatureCalculatorTest.kt index 28b44e14f..2ce1de1d0 100644 --- a/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultSignatureCalculatorTest.kt +++ b/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultSignatureCalculatorTest.kt @@ -7,9 +7,9 @@ package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awssigning.tests.DEFAULT_TEST_CREDENTIALS import aws.smithy.kotlin.runtime.hashing.sha256 +import aws.smithy.kotlin.runtime.text.encoding.decodeHexBytes +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.decodeHexBytes -import aws.smithy.kotlin.runtime.util.encodeToHex import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt index e994760df..2799d4f25 100644 --- a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt +++ b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt @@ -8,6 +8,7 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.auth.awssigning.* +import aws.smithy.kotlin.runtime.collections.ValuesMap import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.auth.AwsHttpSigner import aws.smithy.kotlin.runtime.http.auth.SigV4AuthScheme @@ -21,7 +22,6 @@ import aws.smithy.kotlin.runtime.net.fullUriToQueryParameters import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.ValuesMap import aws.smithy.kotlin.runtime.util.get import io.ktor.http.cio.* import io.ktor.util.* diff --git a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt index 6f893581b..c3f3311b8 100644 --- a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt +++ b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt @@ -9,9 +9,9 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.auth.awssigning.* import aws.smithy.kotlin.runtime.io.SdkBuffer import aws.smithy.kotlin.runtime.operation.ExecutionContext +import aws.smithy.kotlin.runtime.text.encoding.decodeHexBytes import aws.smithy.kotlin.runtime.time.Clock import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.decodeHexBytes import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Message.kt b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Message.kt index ba11fb2c9..3f1f8ce11 100644 --- a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Message.kt +++ b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Message.kt @@ -8,7 +8,7 @@ package aws.smithy.kotlin.runtime.awsprotocol.eventstream import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.hashing.Crc32 import aws.smithy.kotlin.runtime.io.* -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex internal const val MESSAGE_CRC_BYTE_LEN = 4 diff --git a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Prelude.kt b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Prelude.kt index 4835fa86e..0217c224d 100644 --- a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Prelude.kt +++ b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Prelude.kt @@ -8,7 +8,7 @@ package aws.smithy.kotlin.runtime.awsprotocol.eventstream import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.hashing.Crc32 import aws.smithy.kotlin.runtime.io.* -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex internal const val PRELUDE_BYTE_LEN = 8 internal const val PRELUDE_BYTE_LEN_WITH_CRC = PRELUDE_BYTE_LEN + 4 diff --git a/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt b/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt index b67e22dec..01e46fc95 100644 --- a/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt +++ b/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt @@ -11,10 +11,10 @@ import aws.smithy.kotlin.runtime.auth.awssigning.* import aws.smithy.kotlin.runtime.hashing.sha256 import aws.smithy.kotlin.runtime.io.SdkBuffer import aws.smithy.kotlin.runtime.operation.ExecutionContext +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.ManualClock import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.encodeToHex import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/StreamingRequestBodyTest.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/StreamingRequestBodyTest.kt index e807f28c1..73bf3ed8f 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/StreamingRequestBodyTest.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/StreamingRequestBodyTest.kt @@ -9,7 +9,7 @@ import aws.smithy.kotlin.runtime.hashing.sha256 import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.io.* import aws.smithy.kotlin.runtime.testing.RandomTempFile -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import okio.Buffer diff --git a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt index 4bf559d63..3027549ff 100644 --- a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt @@ -16,7 +16,7 @@ import aws.smithy.kotlin.runtime.http.test.util.test import aws.smithy.kotlin.runtime.http.test.util.testSetup import aws.smithy.kotlin.runtime.http.toSdkByteReadChannel import aws.smithy.kotlin.runtime.io.* -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlin.test.* class DownloadTest : AbstractEngineTest() { diff --git a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt index 1c840e359..6dab2622e 100644 --- a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt @@ -15,7 +15,7 @@ import aws.smithy.kotlin.runtime.http.test.util.AbstractEngineTest import aws.smithy.kotlin.runtime.http.test.util.test import aws.smithy.kotlin.runtime.http.test.util.testSetup import aws.smithy.kotlin.runtime.io.* -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlinx.coroutines.* import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Downloads.kt b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Downloads.kt index 9ca311777..c7cceba27 100644 --- a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Downloads.kt +++ b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Downloads.kt @@ -6,7 +6,7 @@ package aws.smithy.kotlin.runtime.http.test.suite import aws.smithy.kotlin.runtime.hashing.sha256 -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import io.ktor.http.* import io.ktor.http.content.* import io.ktor.server.application.* diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Uploads.kt b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Uploads.kt index e77264f5e..aeea40bd9 100644 --- a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Uploads.kt +++ b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Uploads.kt @@ -6,7 +6,7 @@ package aws.smithy.kotlin.runtime.http.test.suite import aws.smithy.kotlin.runtime.hashing.Sha256 -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt index f02c119a4..1e27b4666 100644 --- a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt @@ -15,7 +15,7 @@ import aws.smithy.kotlin.runtime.http.test.util.AbstractEngineTest import aws.smithy.kotlin.runtime.http.test.util.test import aws.smithy.kotlin.runtime.http.test.util.testSetup import aws.smithy.kotlin.runtime.testing.RandomTempFile -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.fail diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptor.kt index 9e357e9a3..7ee18cdf7 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptor.kt @@ -15,8 +15,10 @@ import aws.smithy.kotlin.runtime.http.request.header import aws.smithy.kotlin.runtime.http.request.toBuilder import aws.smithy.kotlin.runtime.io.* import aws.smithy.kotlin.runtime.telemetry.logging.logger -import aws.smithy.kotlin.runtime.util.* -import kotlinx.coroutines.* +import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String +import aws.smithy.kotlin.runtime.util.LazyAsyncValue +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.job import kotlin.coroutines.coroutineContext /** diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt index 3849e8951..8198209f8 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt @@ -18,8 +18,8 @@ import aws.smithy.kotlin.runtime.http.toHashingBody import aws.smithy.kotlin.runtime.http.toHttpBody import aws.smithy.kotlin.runtime.io.* import aws.smithy.kotlin.runtime.telemetry.logging.logger +import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.encodeBase64String import kotlin.coroutines.coroutineContext // The priority to validate response checksums, if multiple are present diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptor.kt index 7b9210a99..cdb122071 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptor.kt @@ -12,7 +12,7 @@ import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.header import aws.smithy.kotlin.runtime.http.request.toBuilder -import aws.smithy.kotlin.runtime.util.encodeBase64String +import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String /** * Set the `Content-MD5` header based on the current payload diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt index d8c978b41..c7064451a 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt @@ -15,7 +15,7 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.headers import aws.smithy.kotlin.runtime.httptest.TestEngine import aws.smithy.kotlin.runtime.io.* -import aws.smithy.kotlin.runtime.util.encodeBase64String +import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest diff --git a/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt b/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt index eb3ae73d2..094231402 100644 --- a/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt +++ b/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt @@ -11,7 +11,7 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.net.Url -import aws.smithy.kotlin.runtime.util.decodeBase64Bytes +import aws.smithy.kotlin.runtime.text.encoding.decodeBase64Bytes import kotlinx.serialization.json.* internal fun parseHttpTraffic(json: String) = buildTestConnection { diff --git a/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/RecordingConnection.kt b/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/RecordingConnection.kt index 11972561f..b7731d696 100644 --- a/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/RecordingConnection.kt +++ b/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/RecordingConnection.kt @@ -15,7 +15,7 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.http.response.copy import aws.smithy.kotlin.runtime.operation.ExecutionContext -import aws.smithy.kotlin.runtime.util.encodeBase64String +import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* diff --git a/runtime/protocol/http/api/http.api b/runtime/protocol/http/api/http.api index edef03758..23f525eb6 100644 --- a/runtime/protocol/http/api/http.api +++ b/runtime/protocol/http/api/http.api @@ -1,4 +1,4 @@ -public abstract interface class aws/smithy/kotlin/runtime/http/DeferredHeaders : aws/smithy/kotlin/runtime/util/ValuesMap { +public abstract interface class aws/smithy/kotlin/runtime/http/DeferredHeaders : aws/smithy/kotlin/runtime/collections/ValuesMap { public static final field Companion Laws/smithy/kotlin/runtime/http/DeferredHeaders$Companion; } @@ -13,11 +13,11 @@ public final class aws/smithy/kotlin/runtime/http/DeferredHeaders$DefaultImpls { public static fun get (Laws/smithy/kotlin/runtime/http/DeferredHeaders;Ljava/lang/String;)Lkotlinx/coroutines/Deferred; } -public final class aws/smithy/kotlin/runtime/http/DeferredHeadersBuilder : aws/smithy/kotlin/runtime/util/ValuesMapBuilder, aws/smithy/kotlin/runtime/util/CanDeepCopy { +public final class aws/smithy/kotlin/runtime/http/DeferredHeadersBuilder : aws/smithy/kotlin/runtime/collections/ValuesMapBuilder, aws/smithy/kotlin/runtime/util/CanDeepCopy { public fun ()V public final fun add (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun build ()Laws/smithy/kotlin/runtime/collections/ValuesMap; public fun build ()Laws/smithy/kotlin/runtime/http/DeferredHeaders; - public synthetic fun build ()Laws/smithy/kotlin/runtime/util/ValuesMap; public fun deepCopy ()Laws/smithy/kotlin/runtime/http/DeferredHeadersBuilder; public synthetic fun deepCopy ()Ljava/lang/Object; } @@ -25,7 +25,7 @@ public final class aws/smithy/kotlin/runtime/http/DeferredHeadersBuilder : aws/s public final class aws/smithy/kotlin/runtime/http/DeferredHeadersKt { } -public abstract interface class aws/smithy/kotlin/runtime/http/Headers : aws/smithy/kotlin/runtime/util/ValuesMap { +public abstract interface class aws/smithy/kotlin/runtime/http/Headers : aws/smithy/kotlin/runtime/collections/ValuesMap { public static final field Companion Laws/smithy/kotlin/runtime/http/Headers$Companion; } @@ -40,10 +40,10 @@ public final class aws/smithy/kotlin/runtime/http/Headers$DefaultImpls { public static fun get (Laws/smithy/kotlin/runtime/http/Headers;Ljava/lang/String;)Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/http/HeadersBuilder : aws/smithy/kotlin/runtime/util/ValuesMapBuilder, aws/smithy/kotlin/runtime/util/CanDeepCopy { +public final class aws/smithy/kotlin/runtime/http/HeadersBuilder : aws/smithy/kotlin/runtime/collections/ValuesMapBuilder, aws/smithy/kotlin/runtime/util/CanDeepCopy { public fun ()V + public synthetic fun build ()Laws/smithy/kotlin/runtime/collections/ValuesMap; public fun build ()Laws/smithy/kotlin/runtime/http/Headers; - public synthetic fun build ()Laws/smithy/kotlin/runtime/util/ValuesMap; public fun deepCopy ()Laws/smithy/kotlin/runtime/http/HeadersBuilder; public synthetic fun deepCopy ()Ljava/lang/Object; public fun toString ()Ljava/lang/String; diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/DeferredHeaders.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/DeferredHeaders.kt index 6041b1844..d410be45e 100644 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/DeferredHeaders.kt +++ b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/DeferredHeaders.kt @@ -5,7 +5,11 @@ package aws.smithy.kotlin.runtime.http import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.util.* +import aws.smithy.kotlin.runtime.collections.ValuesMap +import aws.smithy.kotlin.runtime.collections.ValuesMapBuilder +import aws.smithy.kotlin.runtime.collections.ValuesMapImpl +import aws.smithy.kotlin.runtime.collections.deepCopy +import aws.smithy.kotlin.runtime.util.CanDeepCopy import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/Headers.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/Headers.kt index d83a02e03..d73832f61 100644 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/Headers.kt +++ b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/Headers.kt @@ -4,7 +4,11 @@ */ package aws.smithy.kotlin.runtime.http -import aws.smithy.kotlin.runtime.util.* +import aws.smithy.kotlin.runtime.collections.ValuesMap +import aws.smithy.kotlin.runtime.collections.ValuesMapBuilder +import aws.smithy.kotlin.runtime.collections.ValuesMapImpl +import aws.smithy.kotlin.runtime.collections.deepCopy +import aws.smithy.kotlin.runtime.util.CanDeepCopy /** * Immutable mapping of case insensitive HTTP header names to list of [String] values. diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt index f963c0ba9..43d5e6aa2 100644 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt +++ b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.http.util import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.util.text.VALID_PCHAR_DELIMS -import aws.smithy.kotlin.runtime.util.text.encodeUrlPath +import aws.smithy.kotlin.runtime.text.VALID_PCHAR_DELIMS +import aws.smithy.kotlin.runtime.text.encodeUrlPath // RFC-3986 §3.3 allows sub-delims (defined in section2.2) to be in the path component. // This includes both colon ':' and comma ',' characters. diff --git a/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt b/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt index 773bd7e66..7d8b87280 100644 --- a/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt +++ b/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.http.util -import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.text.urlEncodeComponent import kotlin.test.* class TextTest { diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index c961324ec..4e9308ccb 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -70,6 +70,27 @@ public final class aws/smithy/kotlin/runtime/ServiceException$ErrorType : java/l public static fun values ()[Laws/smithy/kotlin/runtime/ServiceException$ErrorType; } +public abstract interface class aws/smithy/kotlin/runtime/collections/ValuesMap { + public abstract fun contains (Ljava/lang/String;)Z + public abstract fun contains (Ljava/lang/String;Ljava/lang/Object;)Z + public abstract fun entries ()Ljava/util/Set; + public abstract fun forEach (Lkotlin/jvm/functions/Function2;)V + public abstract fun get (Ljava/lang/String;)Ljava/lang/Object; + public abstract fun getAll (Ljava/lang/String;)Ljava/util/List; + public abstract fun getCaseInsensitiveName ()Z + public abstract fun isEmpty ()Z + public abstract fun names ()Ljava/util/Set; +} + +public final class aws/smithy/kotlin/runtime/collections/ValuesMap$DefaultImpls { + public static fun contains (Laws/smithy/kotlin/runtime/collections/ValuesMap;Ljava/lang/String;Ljava/lang/Object;)Z + public static fun forEach (Laws/smithy/kotlin/runtime/collections/ValuesMap;Lkotlin/jvm/functions/Function2;)V + public static fun get (Laws/smithy/kotlin/runtime/collections/ValuesMap;Ljava/lang/String;)Ljava/lang/Object; +} + +public final class aws/smithy/kotlin/runtime/collections/ValuesMapKt { +} + public final class aws/smithy/kotlin/runtime/config/EnvironmentSettingKt { } @@ -609,7 +630,7 @@ public final class aws/smithy/kotlin/runtime/net/HostResolver$DefaultImpls { public static synthetic fun purgeCache$default (Laws/smithy/kotlin/runtime/net/HostResolver;Laws/smithy/kotlin/runtime/net/HostAddress;ILjava/lang/Object;)V } -public abstract interface class aws/smithy/kotlin/runtime/net/QueryParameters : aws/smithy/kotlin/runtime/util/ValuesMap { +public abstract interface class aws/smithy/kotlin/runtime/net/QueryParameters : aws/smithy/kotlin/runtime/collections/ValuesMap { public static final field Companion Laws/smithy/kotlin/runtime/net/QueryParameters$Companion; } @@ -624,10 +645,10 @@ public final class aws/smithy/kotlin/runtime/net/QueryParameters$DefaultImpls { public static fun get (Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/String;)Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/net/QueryParametersBuilder : aws/smithy/kotlin/runtime/util/ValuesMapBuilder, aws/smithy/kotlin/runtime/util/CanDeepCopy { +public final class aws/smithy/kotlin/runtime/net/QueryParametersBuilder : aws/smithy/kotlin/runtime/collections/ValuesMapBuilder, aws/smithy/kotlin/runtime/util/CanDeepCopy { public fun ()V + public synthetic fun build ()Laws/smithy/kotlin/runtime/collections/ValuesMap; public fun build ()Laws/smithy/kotlin/runtime/net/QueryParameters; - public synthetic fun build ()Laws/smithy/kotlin/runtime/util/ValuesMap; public fun deepCopy ()Laws/smithy/kotlin/runtime/net/QueryParametersBuilder; public synthetic fun deepCopy ()Ljava/lang/Object; public fun toString ()Ljava/lang/String; @@ -799,18 +820,18 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters : java/u public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/QueryParameters$Companion; public synthetic fun (Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun clear ()V - public fun compute (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; + public fun compute (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun computeIfAbsent (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/function/Function;)Ljava/util/List; + public fun computeIfAbsent (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/function/Function;)Ljava/util/List; public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; - public fun computeIfPresent (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; + public fun computeIfPresent (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun containsKey (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Z + public fun containsKey (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z public final fun containsKey (Ljava/lang/Object;)Z public final fun containsValue (Ljava/lang/Object;)Z public fun containsValue (Ljava/util/List;)Z public final fun entrySet ()Ljava/util/Set; - public fun get (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Ljava/util/List; + public fun get (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public final fun get (Ljava/lang/Object;)Ljava/util/List; public fun getEntries ()Ljava/util/Set; @@ -819,18 +840,18 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters : java/u public fun getValues ()Ljava/util/Collection; public fun isEmpty ()Z public final fun keySet ()Ljava/util/Set; - public fun merge (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;Ljava/util/function/BiFunction;)Ljava/util/List; + public fun merge (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;Ljava/util/function/BiFunction;)Ljava/util/List; public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun put (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public fun put (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun putAll (Ljava/util/Map;)V - public fun putIfAbsent (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public fun putIfAbsent (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; public fun remove (Ljava/lang/Object;)Ljava/util/List; public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replace (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; - public fun replace (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;Ljava/util/List;)Z + public fun replace (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public fun replace (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;Ljava/util/List;)Z public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z public fun replaceAll (Ljava/util/function/BiFunction;)V @@ -844,12 +865,12 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder public fun ()V public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; public fun clear ()V - public fun containsKey (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Z + public fun containsKey (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z public final fun containsKey (Ljava/lang/Object;)Z public final fun containsValue (Ljava/lang/Object;)Z public fun containsValue (Ljava/util/List;)Z public final fun entrySet ()Ljava/util/Set; - public fun get (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Ljava/util/List; + public fun get (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public final fun get (Ljava/lang/Object;)Ljava/util/List; public fun getEntries ()Ljava/util/Set; @@ -858,10 +879,10 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder public fun getValues ()Ljava/util/Collection; public fun isEmpty ()Z public final fun keySet ()Ljava/util/Set; - public fun put (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; + public fun put (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun putAll (Ljava/util/Map;)V - public fun remove (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;)Ljava/util/List; + public fun remove (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; public final fun remove (Ljava/lang/Object;)Ljava/util/List; public final fun size ()I @@ -876,8 +897,8 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Companio public final class aws/smithy/kotlin/runtime/net/newnet/Url { public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/Url$Companion; - public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILaws/smithy/kotlin/runtime/net/newnet/UrlPath;Laws/smithy/kotlin/runtime/net/newnet/QueryParameters;Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Laws/smithy/kotlin/runtime/net/newnet/UserInfo;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getFragment ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; + public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILaws/smithy/kotlin/runtime/net/newnet/UrlPath;Laws/smithy/kotlin/runtime/net/newnet/QueryParameters;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/net/newnet/UserInfo;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getFragment ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; public final fun getPath ()Laws/smithy/kotlin/runtime/net/newnet/UrlPath; public final fun getPort ()I @@ -948,9 +969,9 @@ public final class aws/smithy/kotlin/runtime/net/newnet/UrlPath$Companion { public final class aws/smithy/kotlin/runtime/net/newnet/UserInfo { public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/UserInfo$Companion; - public synthetic fun (Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Laws/smithy/kotlin/runtime/util/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getPassword ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; - public final fun getUserName ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; + public synthetic fun (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getPassword ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; + public final fun getUserName ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/UserInfo$Builder; public fun toString ()Ljava/lang/String; } @@ -1384,6 +1405,43 @@ public final class aws/smithy/kotlin/runtime/retries/policy/StandardRetryPolicy$ public final fun getDefault ()Laws/smithy/kotlin/runtime/retries/policy/StandardRetryPolicy; } +public final class aws/smithy/kotlin/runtime/text/TextKt { +} + +public final class aws/smithy/kotlin/runtime/text/Utf8Kt { +} + +public final class aws/smithy/kotlin/runtime/text/encoding/Base64Kt { + public static final fun decodeBase64 (Ljava/lang/String;)Ljava/lang/String; + public static final fun decodeBase64 ([B)[B + public static final fun decodeBase64Bytes (Ljava/lang/String;)[B + public static final fun encodeBase64 (Ljava/lang/String;)Ljava/lang/String; + public static final fun encodeBase64 ([B)[B + public static final fun encodeBase64String ([B)Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/text/encoding/Encodable { + public static final field Companion Laws/smithy/kotlin/runtime/text/encoding/Encodable$Companion; + public fun equals (Ljava/lang/Object;)Z + public final fun getDecoded ()Ljava/lang/String; + public final fun getEncoded ()Ljava/lang/String; + public final fun getEncoding ()Laws/smithy/kotlin/runtime/text/encoding/Encoding; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/text/encoding/Encodable$Companion { + public final fun getEmpty ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; +} + +public final class aws/smithy/kotlin/runtime/text/encoding/Encoding$DefaultImpls { + public static fun encodableFromDecoded (Laws/smithy/kotlin/runtime/text/encoding/Encoding;Ljava/lang/String;)Laws/smithy/kotlin/runtime/text/encoding/Encodable; + public static fun encodableFromEncoded (Laws/smithy/kotlin/runtime/text/encoding/Encoding;Ljava/lang/String;)Laws/smithy/kotlin/runtime/text/encoding/Encodable; +} + +public final class aws/smithy/kotlin/runtime/text/encoding/HexKt { +} + public abstract interface class aws/smithy/kotlin/runtime/time/Clock { public static final field Companion Laws/smithy/kotlin/runtime/time/Clock$Companion; public abstract fun now ()Laws/smithy/kotlin/runtime/time/Instant; @@ -1490,15 +1548,6 @@ public final class aws/smithy/kotlin/runtime/util/AttributesKt { public static final fun toMutableAttributes (Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/util/MutableAttributes; } -public final class aws/smithy/kotlin/runtime/util/Base64Kt { - public static final fun decodeBase64 (Ljava/lang/String;)Ljava/lang/String; - public static final fun decodeBase64 ([B)[B - public static final fun decodeBase64Bytes (Ljava/lang/String;)[B - public static final fun encodeBase64 (Ljava/lang/String;)Ljava/lang/String; - public static final fun encodeBase64 ([B)[B - public static final fun encodeBase64String ([B)Ljava/lang/String; -} - public abstract interface class aws/smithy/kotlin/runtime/util/Buildable { public abstract fun build ()Ljava/lang/Object; } @@ -1531,9 +1580,6 @@ public final class aws/smithy/kotlin/runtime/util/Filesystem$Companion { public static synthetic fun fromMap$default (Laws/smithy/kotlin/runtime/util/Filesystem$Companion;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/util/Filesystem; } -public final class aws/smithy/kotlin/runtime/util/HexKt { -} - public final class aws/smithy/kotlin/runtime/util/JMESPathKt { } @@ -1599,27 +1645,6 @@ public final class aws/smithy/kotlin/runtime/util/StackKt { public final class aws/smithy/kotlin/runtime/util/TestPlatformProvider$Companion { } -public abstract interface class aws/smithy/kotlin/runtime/util/ValuesMap { - public abstract fun contains (Ljava/lang/String;)Z - public abstract fun contains (Ljava/lang/String;Ljava/lang/Object;)Z - public abstract fun entries ()Ljava/util/Set; - public abstract fun forEach (Lkotlin/jvm/functions/Function2;)V - public abstract fun get (Ljava/lang/String;)Ljava/lang/Object; - public abstract fun getAll (Ljava/lang/String;)Ljava/util/List; - public abstract fun getCaseInsensitiveName ()Z - public abstract fun isEmpty ()Z - public abstract fun names ()Ljava/util/Set; -} - -public final class aws/smithy/kotlin/runtime/util/ValuesMap$DefaultImpls { - public static fun contains (Laws/smithy/kotlin/runtime/util/ValuesMap;Ljava/lang/String;Ljava/lang/Object;)Z - public static fun forEach (Laws/smithy/kotlin/runtime/util/ValuesMap;Lkotlin/jvm/functions/Function2;)V - public static fun get (Laws/smithy/kotlin/runtime/util/ValuesMap;Ljava/lang/String;)Ljava/lang/Object; -} - -public final class aws/smithy/kotlin/runtime/util/ValuesMapKt { -} - public final class aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView : java/util/ListIterator, kotlin/jvm/internal/markers/KMutableListIterator { public fun (Ljava/util/ListIterator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public fun add (Ljava/lang/Object;)V @@ -1633,28 +1658,3 @@ public final class aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorVie public fun set (Ljava/lang/Object;)V } -public final class aws/smithy/kotlin/runtime/util/text/TextKt { -} - -public final class aws/smithy/kotlin/runtime/util/text/Utf8Kt { -} - -public final class aws/smithy/kotlin/runtime/util/text/encoding/Encodable { - public static final field Companion Laws/smithy/kotlin/runtime/util/text/encoding/Encodable$Companion; - public fun equals (Ljava/lang/Object;)Z - public final fun getDecoded ()Ljava/lang/String; - public final fun getEncoded ()Ljava/lang/String; - public final fun getEncoding ()Laws/smithy/kotlin/runtime/util/text/encoding/Encoding; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/util/text/encoding/Encodable$Companion { - public final fun getEmpty ()Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; -} - -public final class aws/smithy/kotlin/runtime/util/text/encoding/Encoding$DefaultImpls { - public static fun encodableFromDecoded (Laws/smithy/kotlin/runtime/util/text/encoding/Encoding;Ljava/lang/String;)Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; - public static fun encodableFromEncoded (Laws/smithy/kotlin/runtime/util/text/encoding/Encoding;Ljava/lang/String;)Laws/smithy/kotlin/runtime/util/text/encoding/Encodable; -} - diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt new file mode 100644 index 000000000..a0d7635e2 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections + +import aws.smithy.kotlin.runtime.InternalApi + +/** + * A mutable view of a mutable iterator. This class presents the elements of a source iterator in a different + * format/type. Updates to this iterator (i.e., advancing to the next element or removing the current element) are + * propagated to the source iterator. + * @param Src The type of elements in the source iterator + * @param Dest The type of elements in this view + * @param srcIterator The source iterator containing the canonical elements + * @param srcToDest A function that transforms a [Src] object to a [Dest] + */ +@InternalApi +public class MutableIteratorView( + private val srcIterator: MutableIterator, + private val srcToDest: (Src) -> Dest, +) : MutableIterator { + override fun hasNext(): Boolean = srcIterator.hasNext() + + override fun next(): Dest = srcToDest(srcIterator.next()) + + override fun remove() { + srcIterator.remove() + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt new file mode 100644 index 000000000..d93e172db --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections + +import aws.smithy.kotlin.runtime.InternalApi + +/** + * A mutable view of a mutable list iterator. This class presents the elements of a source iterator in a different + * format/type. Updates to this iterator (e.g., advancing to the next element, additions, removals, etc.) are propagated + * to the source iterator. + * @param Src The type of elements in the source iterator + * @param Dest The type of elements in this view + * @param srcIterator The source iterator containing the canonical elements + * @param srcToDest A function that transforms a [Src] object to a [Dest] + * @param destToSrc A function that transforms a [Dest] object to a [Src] + */ +@InternalApi +public class MutableListIteratorView( + private val srcIterator: MutableListIterator, + private val srcToDest: (Src) -> Dest, + private val destToSrc: (Dest) -> Src, +) : MutableListIterator { + override fun add(element: Dest) { + srcIterator.add(destToSrc(element)) + } + + override fun hasNext(): Boolean = srcIterator.hasNext() + + override fun hasPrevious(): Boolean = srcIterator.hasPrevious() + + override fun next(): Dest = srcToDest(srcIterator.next()) + + override fun nextIndex(): Int = srcIterator.nextIndex() + + override fun previous(): Dest = srcToDest(srcIterator.previous()) + + override fun previousIndex(): Int = srcIterator.previousIndex() + + override fun remove() { + srcIterator.remove() + } + + override fun set(element: Dest) { + srcIterator.set(destToSrc(element)) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt new file mode 100644 index 000000000..05d4c68be --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections + +import aws.smithy.kotlin.runtime.InternalApi + +/** + * A mutable view of a mutable list. This class presents the elements of a source list in a different format/type. + * Updates to this view (e.g., additions, removals, etc.) are propagated to the source list. + * @param Src The type of elements in the source list + * @param Dest The type of elements in this view + * @param srcList The source list containing the canonical elements + * @param srcToDest A function that transforms a [Src] object to a [Dest] + * @param destToSrc A function that transforms a [Dest] object to a [Src] + */ +@InternalApi +public class MutableListView( + private val srcList: MutableList, + private val srcToDest: (Src) -> Dest, + private val destToSrc: (Dest) -> Src, +) : MutableList { + override fun add(element: Dest): Boolean = srcList.add(destToSrc(element)) + + override fun add(index: Int, element: Dest) { + srcList.add(index, destToSrc(element)) + } + + override fun addAll(elements: Collection): Boolean = srcList.addAll(elements.map(destToSrc)) + + override fun addAll(index: Int, elements: Collection): Boolean = + srcList.addAll(index, elements.map(destToSrc)) + + override fun clear() { + srcList.clear() + } + + override fun contains(element: Dest): Boolean = srcList.contains(destToSrc(element)) + + override fun containsAll(elements: Collection): Boolean = srcList.containsAll(elements.map(destToSrc)) + + override fun get(index: Int): Dest = srcToDest(srcList[index]) + + override fun indexOf(element: Dest): Int = srcList.indexOf(destToSrc(element)) + + override fun isEmpty(): Boolean = srcList.isEmpty() + + override fun iterator(): MutableIterator = MutableIteratorView(srcList.iterator(), srcToDest) + + override fun lastIndexOf(element: Dest): Int = srcList.lastIndexOf(destToSrc(element)) + + override fun listIterator(): MutableListIterator = + MutableListIteratorView(srcList.listIterator(), srcToDest, destToSrc) + + override fun listIterator(index: Int): MutableListIterator = + MutableListIteratorView(srcList.listIterator(index), srcToDest, destToSrc) + + override fun remove(element: Dest): Boolean = srcList.remove(destToSrc(element)) + + override fun removeAll(elements: Collection): Boolean = srcList.removeAll(elements.map(destToSrc)) + + override fun removeAt(index: Int): Dest = srcToDest(srcList.removeAt(index)) + + override fun retainAll(elements: Collection): Boolean = srcList.retainAll(elements.map(destToSrc)) + + override fun set(index: Int, element: Dest): Dest = srcToDest(srcList.set(index, destToSrc(element))) + + override val size: Int + get() = srcList.size + + override fun subList(fromIndex: Int, toIndex: Int): MutableList = MutableListView( + srcList.subList(fromIndex, toIndex), + srcToDest, + destToSrc, + ) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/ValuesMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ValuesMap.kt similarity index 98% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/ValuesMap.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ValuesMap.kt index 5559a2398..e15e6966a 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/ValuesMap.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ValuesMap.kt @@ -2,9 +2,10 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.util.CaseInsensitiveMap /** * Mapping of [String] to a List of [T] values diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt index 364e7d757..afc1d7ee6 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.net -import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.text.urlEncodeComponent /** * A [Host] represents a parsed internet host. This may be an internet address (IPv4, IPv6) or a domain name. diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt index 6835fa9b7..083ed6ff6 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt @@ -5,9 +5,13 @@ package aws.smithy.kotlin.runtime.net import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.util.* -import aws.smithy.kotlin.runtime.util.text.splitAsQueryString -import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.collections.ValuesMap +import aws.smithy.kotlin.runtime.collections.ValuesMapBuilder +import aws.smithy.kotlin.runtime.collections.ValuesMapImpl +import aws.smithy.kotlin.runtime.collections.deepCopy +import aws.smithy.kotlin.runtime.text.splitAsQueryString +import aws.smithy.kotlin.runtime.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.util.CanDeepCopy /** * Container for HTTP query parameters diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt index b21c46949..1e012a12b 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt @@ -5,10 +5,10 @@ package aws.smithy.kotlin.runtime.net import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.text.encodeUrlPath +import aws.smithy.kotlin.runtime.text.urlDecodeComponent +import aws.smithy.kotlin.runtime.text.urlEncodeComponent import aws.smithy.kotlin.runtime.util.CanDeepCopy -import aws.smithy.kotlin.runtime.util.text.encodeUrlPath -import aws.smithy.kotlin.runtime.util.text.urlDecodeComponent -import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent /** * Represents an immutable URL of the form: `scheme://[userinfo@]host[:port][/path][?query][#fragment]` diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt index 27f1831da..119bf9153 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt @@ -4,8 +4,8 @@ */ package aws.smithy.kotlin.runtime.net -import aws.smithy.kotlin.runtime.util.text.splitAsQueryString -import aws.smithy.kotlin.runtime.util.text.urlDecodeComponent +import aws.smithy.kotlin.runtime.text.splitAsQueryString +import aws.smithy.kotlin.runtime.text.urlDecodeComponent internal fun urlParseImpl(url: String, decoding: UrlDecoding): Url = UrlBuilder { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt index f8e8327e3..939ce3865 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt @@ -4,8 +4,8 @@ */ package aws.smithy.kotlin.runtime.net.newnet -import aws.smithy.kotlin.runtime.util.text.encoding.Encodable -import aws.smithy.kotlin.runtime.util.text.encoding.Encoding +import aws.smithy.kotlin.runtime.text.encoding.Encodable +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding /** * Represents the parameters in a URL query string. @@ -68,8 +68,8 @@ public class QueryParameters private constructor(private val delegate: Map Encodable) { clear() diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt index 1cdf484f1..4a1aa2112 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt @@ -8,9 +8,9 @@ import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme import aws.smithy.kotlin.runtime.net.splitHostPort import aws.smithy.kotlin.runtime.net.toUrlString -import aws.smithy.kotlin.runtime.util.text.Scanner -import aws.smithy.kotlin.runtime.util.text.encoding.Encodable -import aws.smithy.kotlin.runtime.util.text.encoding.Encoding +import aws.smithy.kotlin.runtime.text.Scanner +import aws.smithy.kotlin.runtime.text.encoding.Encodable +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding /** * Represents a full, valid URL @@ -216,14 +216,14 @@ public class Url private constructor( */ public var fragmentDecoded: String? get() = fragment?.decoded - set(value) { fragment = value?.let(Encoding.Fragment::encodableFromDecoded) } + set(value) { fragment = value?.let(PercentEncoding.Fragment::encodableFromDecoded) } /** * Get or set the fragment as an **encoded** string */ public var fragmentEncoded: String? get() = fragment?.encoded - set(value) { fragment = value?.let(Encoding.Fragment::encodableFromEncoded) } + set(value) { fragment = value?.let(PercentEncoding.Fragment::encodableFromEncoded) } // User info diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt index 1ec4c6acb..a9f39ae17 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt @@ -4,9 +4,9 @@ */ package aws.smithy.kotlin.runtime.net.newnet -import aws.smithy.kotlin.runtime.util.newcoll.MutableListView -import aws.smithy.kotlin.runtime.util.text.encoding.Encodable -import aws.smithy.kotlin.runtime.util.text.encoding.Encoding +import aws.smithy.kotlin.runtime.collections.MutableListView +import aws.smithy.kotlin.runtime.text.encoding.Encodable +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding /** * Represents the path component of a URL @@ -85,7 +85,7 @@ public class UrlPath private constructor(public val segments: List, p public val decodedSegments: MutableList = MutableListView( segments, Encodable::decoded, - Encoding.Query::encodableFromDecoded, + PercentEncoding.Query::encodableFromDecoded, ) /** @@ -94,7 +94,7 @@ public class UrlPath private constructor(public val segments: List, p public val encodedSegments: MutableList = MutableListView( segments, Encodable::encoded, - Encoding.Query::encodableFromEncoded, + PercentEncoding.Query::encodableFromEncoded, ) /** @@ -105,8 +105,8 @@ public class UrlPath private constructor(public val segments: List, p internal fun asDecoded() = asDecoded(segments, trailingSlash) internal fun asEncoded() = asEncoded(segments, trailingSlash) - internal fun parseDecoded(decoded: String): Unit = parse(decoded, Encoding.Path::encodableFromDecoded) - internal fun parseEncoded(encoded: String): Unit = parse(encoded, Encoding.Path::encodableFromEncoded) + internal fun parseDecoded(decoded: String): Unit = parse(decoded, PercentEncoding.Path::encodableFromDecoded) + internal fun parseEncoded(encoded: String): Unit = parse(encoded, PercentEncoding.Path::encodableFromEncoded) private fun parse(text: String, toEncodable: (String) -> Encodable) { segments.clear() diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt index ea359cbf6..ac0f1811d 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt @@ -4,12 +4,12 @@ */ package aws.smithy.kotlin.runtime.net.newnet -import aws.smithy.kotlin.runtime.util.text.encoding.Encodable -import aws.smithy.kotlin.runtime.util.text.encoding.Encoding +import aws.smithy.kotlin.runtime.text.encoding.Encodable +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding /** * Represents the user authentication information in a URL - * @param userName The user name of the caller + * @param userName The username of the caller * @param password The password for the caller */ public class UserInfo private constructor(public val userName: Encodable, public val password: Encodable) { @@ -57,24 +57,24 @@ public class UserInfo private constructor(public val userName: Encodable, public public var userNameDecoded: String get() = userName.decoded - set(value) { userName = Encoding.UserInfo.encodableFromDecoded(value) } + set(value) { userName = PercentEncoding.UserInfo.encodableFromDecoded(value) } public var userNameEncoded: String get() = userName.encoded - set(value) { userName = Encoding.UserInfo.encodableFromEncoded(value) } + set(value) { userName = PercentEncoding.UserInfo.encodableFromDecoded(value) } private var password = userInfo?.password ?: Encodable.Empty public var passwordDecoded: String get() = password.decoded - set(value) { password = Encoding.UserInfo.encodableFromDecoded(value) } + set(value) { password = PercentEncoding.UserInfo.encodableFromDecoded(value) } public var passwordEncoded: String get() = password.encoded - set(value) { password = Encoding.UserInfo.encodableFromEncoded(value) } + set(value) { password = PercentEncoding.UserInfo.encodableFromEncoded(value) } - internal fun parseDecoded(decoded: String) = parse(decoded, Encoding.UserInfo::encodableFromDecoded) - internal fun parseEncoded(encoded: String) = parse(encoded, Encoding.UserInfo::encodableFromEncoded) + internal fun parseDecoded(decoded: String) = parse(decoded, PercentEncoding.UserInfo::encodableFromDecoded) + internal fun parseEncoded(encoded: String) = parse(encoded, PercentEncoding.UserInfo::encodableFromEncoded) private fun parse(text: String, toEncodable: (String) -> Encodable) { if (text.isEmpty()) { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Scanner.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Scanner.kt similarity index 99% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Scanner.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Scanner.kt index f56276a09..9eb9a5c0c 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Scanner.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Scanner.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text +package aws.smithy.kotlin.runtime.text import aws.smithy.kotlin.runtime.InternalApi diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Text.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt similarity index 99% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Text.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt index fb3bd514e..9fff2b0d9 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Text.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text +package aws.smithy.kotlin.runtime.text import aws.smithy.kotlin.runtime.InternalApi diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Utf8.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Utf8.kt similarity index 97% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Utf8.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Utf8.kt index c047752be..86860a8ef 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/Utf8.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Utf8.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text +package aws.smithy.kotlin.runtime.text import aws.smithy.kotlin.runtime.InternalApi diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Base64.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Base64.kt similarity index 99% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Base64.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Base64.kt index e133bf97d..7a3a56989 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Base64.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Base64.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.text.encoding private const val BASE64_ENCODE_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" private const val BASE64_PAD_SENTINEL = 0xff diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt similarity index 95% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt index 724c3fbe5..318ee51da 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encodable.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text.encoding +package aws.smithy.kotlin.runtime.text.encoding public class Encodable internal constructor( public val decoded: String, diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt new file mode 100644 index 000000000..c1f4104dc --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.text.encoding + +import aws.smithy.kotlin.runtime.InternalApi + +@InternalApi +public interface Encoding { + @InternalApi + public companion object { + internal val None = object : Encoding { + override val name = "(no encoding)" + override fun decode(encoded: String) = encoded + override fun encode(decoded: String) = decoded + } + } + + public val name: String + + public fun decode(encoded: String): String + public fun encode(decoded: String): String + + public fun encodableFromDecoded(decoded: String): Encodable = Encodable(decoded, encode(decoded), this) + public fun encodableFromEncoded(encoded: String): Encodable { + val decoded = decode(encoded) + val reencoded = encode(decoded) // TODO is this right? + return Encodable(decoded, reencoded, this) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Hex.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Hex.kt similarity index 96% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Hex.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Hex.kt index 312e9671b..29c76e7d2 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Hex.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Hex.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.text.encoding import aws.smithy.kotlin.runtime.InternalApi diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt similarity index 71% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt index f41985b16..719909bd3 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/text/encoding/Encoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt @@ -2,48 +2,10 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text.encoding +package aws.smithy.kotlin.runtime.text.encoding import aws.smithy.kotlin.runtime.InternalApi -private val ALPHA = (('A'..'Z') + ('a'..'z')).toSet() -private val DIGIT = ('0'..'9').toSet() -private val UNRESERVED = ALPHA + DIGIT + setOf('-', '.', '_', '~') -private val SUB_DELIMS = setOf('!', '$', '&', '\'', '(', ')', '*', ',', ';', '=') -private val VALID_UCHAR = UNRESERVED + SUB_DELIMS -private val VALID_PCHAR = VALID_UCHAR + setOf(':', '@') -private val VALID_FCHAR = VALID_PCHAR + setOf('/', '?') -private val VALID_QCHAR = VALID_FCHAR - setOf('&', '=') - -@InternalApi -public interface Encoding { - @InternalApi - public companion object { - public val UserInfo: Encoding = PercentEncoding("user info", VALID_UCHAR) - public val Path: Encoding = PercentEncoding("path", VALID_PCHAR) - public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR, mapOf(' ' to '+')) - public val Fragment: Encoding = PercentEncoding("fragment", VALID_FCHAR) - - internal val None = object : Encoding { - override val name = "(no encoding)" - override fun decode(encoded: String) = encoded - override fun encode(decoded: String) = decoded - } - } - - public val name: String - - public fun decode(encoded: String): String - public fun encode(decoded: String): String - - public fun encodableFromDecoded(decoded: String): Encodable = Encodable(decoded, encode(decoded), this) - public fun encodableFromEncoded(encoded: String): Encodable { - val decoded = decode(encoded) - val reencoded = encode(decoded) // TODO is this right? - return Encodable(decoded, reencoded, this) - } -} - @InternalApi public class PercentEncoding( override val name: String, @@ -52,8 +14,22 @@ public class PercentEncoding( ) : Encoding { @InternalApi public companion object { + private val ALPHA = (('A'..'Z') + ('a'..'z')).toSet() + private val DIGIT = ('0'..'9').toSet() + private val UNRESERVED = ALPHA + DIGIT + setOf('-', '.', '_', '~') + private val SUB_DELIMS = setOf('!', '$', '&', '\'', '(', ')', '*', ',', ';', '=') + private val VALID_UCHAR = UNRESERVED + SUB_DELIMS + private val VALID_PCHAR = VALID_UCHAR + setOf(':', '@') + private val VALID_FCHAR = VALID_PCHAR + setOf('/', '?') + private val VALID_QCHAR = VALID_FCHAR - setOf('&', '=') + private const val UPPER_HEX = "0123456789ABCDEF" + public val UserInfo: Encoding = PercentEncoding("user info", VALID_UCHAR) + public val Path: Encoding = PercentEncoding("path", VALID_PCHAR) + public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR, mapOf(' ' to '+')) + public val Fragment: Encoding = PercentEncoding("fragment", VALID_FCHAR) + private fun percentAsciiEncode(char: Char) = buildString { val value = char.code and 0xff append('%') @@ -73,7 +49,7 @@ public class PercentEncoding( private val asciiMapping = (0..<128) .map(Int::toChar) .filterNot(validChars::contains) - .associateWith(::percentAsciiEncode) + .associateWith(Companion::percentAsciiEncode) private val encodeMap = asciiMapping + specialMapping.mapValues { (_, char) -> char.toString() } diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/ValuesMapTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/ValuesMapTest.kt similarity index 98% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/ValuesMapTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/ValuesMapTest.kt index 6b8a3d5a4..d9fd121b5 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/ValuesMapTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/ValuesMapTest.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32Test.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32Test.kt index 92ae8120e..e3b489aba 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32Test.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32Test.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.hashing -import aws.smithy.kotlin.runtime.util.encodeBase64String +import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32cTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32cTest.kt index 943aa2d8b..184f62cbd 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32cTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Crc32cTest.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.hashing -import aws.smithy.kotlin.runtime.util.encodeBase64String +import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/HmacTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/HmacTest.kt index 61efe1849..eb1f55a8d 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/HmacTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/HmacTest.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.hashing -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Md5Test.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Md5Test.kt index a63ac8417..30d2d8453 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Md5Test.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Md5Test.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.hashing -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha1Test.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha1Test.kt index fc27bcb78..b9b4f1ad0 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha1Test.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha1Test.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.hashing -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha256Test.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha256Test.kt index 9b4276d71..9bd07fd44 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha256Test.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/hashing/Sha256Test.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.hashing -import aws.smithy.kotlin.runtime.util.encodeToHex +import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/TextTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/TextTest.kt similarity index 99% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/TextTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/TextTest.kt index 48b906b20..83297f902 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/TextTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/TextTest.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text +package aws.smithy.kotlin.runtime.text import io.kotest.matchers.maps.shouldContain import kotlin.test.* diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/Utf8Test.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/Utf8Test.kt similarity index 97% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/Utf8Test.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/Utf8Test.kt index ce5df2759..f678210cf 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/Utf8Test.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/Utf8Test.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text +package aws.smithy.kotlin.runtime.text import kotlin.test.* diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/Base64Test.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/Base64Test.kt similarity index 98% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/Base64Test.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/Base64Test.kt index a82175dc6..4c2ada201 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/Base64Test.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/Base64Test.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.text.encoding import io.kotest.matchers.string.shouldContain import kotlin.test.Test diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/HexTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/HexTest.kt similarity index 96% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/HexTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/HexTest.kt index b9cdc3504..466f4f05e 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/HexTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/HexTest.kt @@ -2,8 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.text.encoding import kotlin.test.Test import kotlin.test.assertContentEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/encoding/EncodingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/PercentEncodingTest.kt similarity index 68% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/encoding/EncodingTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/PercentEncodingTest.kt index 2859b3951..84691121d 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/text/encoding/EncodingTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/PercentEncodingTest.kt @@ -2,23 +2,23 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util.text.encoding +package aws.smithy.kotlin.runtime.text.encoding import kotlin.test.Test import kotlin.test.assertEquals -class EncodingTest { +class PercentEncodingTest { @Test fun testUnicodeDecoding() { val encoded = "f%F0%9F%98%81o" - val decoded = Encoding.Query.decode(encoded) + val decoded = PercentEncoding.Query.decode(encoded) assertEquals("f😁o", decoded) } @Test fun testUnicodeEncoding() { val decoded = "f😁o" - val encoded = Encoding.Query.encode(decoded) + val encoded = PercentEncoding.Query.encode(decoded) assertEquals("f%F0%9F%98%81o", encoded) } } diff --git a/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt b/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt index 6a0c6eac7..19f04fc67 100644 --- a/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt +++ b/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt @@ -11,9 +11,9 @@ import aws.smithy.kotlin.runtime.content.BigInteger import aws.smithy.kotlin.runtime.content.Document import aws.smithy.kotlin.runtime.io.SdkBuffer import aws.smithy.kotlin.runtime.serde.* +import aws.smithy.kotlin.runtime.text.urlEncodeComponent import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.TimestampFormat -import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract diff --git a/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/deserialization/XmlLexer.kt b/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/deserialization/XmlLexer.kt index e613ee662..80bba61f4 100644 --- a/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/deserialization/XmlLexer.kt +++ b/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/deserialization/XmlLexer.kt @@ -7,7 +7,7 @@ package aws.smithy.kotlin.runtime.serde.xml.deserialization import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.serde.DeserializationException import aws.smithy.kotlin.runtime.serde.xml.XmlToken -import aws.smithy.kotlin.runtime.util.text.codePointToChars +import aws.smithy.kotlin.runtime.text.codePointToChars private val decimalCharRef = "#([0-9]+)".toRegex() private val hexCharRef = "#x([0-9a-fA-F]+)".toRegex() diff --git a/runtime/smithy-client/api/smithy-client.api b/runtime/smithy-client/api/smithy-client.api index e7c236868..002fdeb51 100644 --- a/runtime/smithy-client/api/smithy-client.api +++ b/runtime/smithy-client/api/smithy-client.api @@ -180,16 +180,16 @@ public final class aws/smithy/kotlin/runtime/client/SdkClientOptionKt { } public final class aws/smithy/kotlin/runtime/client/endpoints/Endpoint { - public fun (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/util/ValuesMap;)V - public synthetic fun (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/util/ValuesMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;)V + public synthetic fun (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Ljava/lang/String;)V public final fun component1 ()Laws/smithy/kotlin/runtime/net/Url; - public final fun component2 ()Laws/smithy/kotlin/runtime/util/ValuesMap; + public final fun component2 ()Laws/smithy/kotlin/runtime/collections/ValuesMap; public final fun component3 ()Laws/smithy/kotlin/runtime/util/Attributes; - public final fun copy (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/util/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/client/endpoints/Endpoint;Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/util/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; + public final fun copy (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/client/endpoints/Endpoint;Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; public fun equals (Ljava/lang/Object;)Z - public final fun getHeaders ()Laws/smithy/kotlin/runtime/util/ValuesMap; + public final fun getHeaders ()Laws/smithy/kotlin/runtime/collections/ValuesMap; public final fun getUri ()Laws/smithy/kotlin/runtime/net/Url; public fun hashCode ()I public fun toString ()Ljava/lang/String; diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt index 916b55e78..e63e07e5f 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt @@ -6,10 +6,10 @@ package aws.smithy.kotlin.runtime.client.endpoints import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.ValuesMap import aws.smithy.kotlin.runtime.net.Url import aws.smithy.kotlin.runtime.util.AttributeKey import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.ValuesMap import aws.smithy.kotlin.runtime.util.emptyAttributes /** diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt index f3d577e70..b8c9a3561 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt @@ -7,8 +7,8 @@ package aws.smithy.kotlin.runtime.client.endpoints.functions import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.net.* -import aws.smithy.kotlin.runtime.util.text.ensureSuffix -import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.text.ensureSuffix +import aws.smithy.kotlin.runtime.text.urlEncodeComponent @InternalApi public fun substring(value: String?, start: Int, stop: Int, reverse: Boolean): String? = diff --git a/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt b/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt index b705ca28b..409342aa7 100644 --- a/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt +++ b/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt @@ -10,7 +10,7 @@ import aws.smithy.kotlin.runtime.http.HttpMethod import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.httptest.TestEngine -import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.text.urlEncodeComponent import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.test.runTest import kotlin.test.assertEquals From e56378faa23734e7e7d690b17f79b1201a766478 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Fri, 10 Nov 2023 00:06:04 +0000 Subject: [PATCH 04/17] Move new URL classes into runtime.net.url package; clean up API based on PR feedback --- runtime/runtime-core/api/runtime-core.api | 133 ++++++++-------- .../net/{newnet => url}/QueryParameters.kt | 71 +++++++-- .../kotlin/runtime/net/{newnet => url}/Url.kt | 143 ++++++++---------- .../runtime/net/{newnet => url}/UrlPath.kt | 33 ++-- .../runtime/net/{newnet => url}/UserInfo.kt | 33 +++- .../kotlin/runtime/text/encoding/Encodable.kt | 20 +-- .../runtime/net/{newnet => url}/UrlTest.kt | 6 +- 7 files changed, 249 insertions(+), 190 deletions(-) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/{newnet => url}/QueryParameters.kt (59%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/{newnet => url}/Url.kt (65%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/{newnet => url}/UrlPath.kt (85%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/{newnet => url}/UserInfo.kt (80%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/{newnet => url}/UrlTest.kt (96%) diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index 4e9308ccb..1833f3fcb 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -816,9 +816,9 @@ public final class aws/smithy/kotlin/runtime/net/UserInfo { public fun toString ()Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { - public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/QueryParameters$Companion; - public synthetic fun (Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class aws/smithy/kotlin/runtime/net/url/QueryParameters : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Laws/smithy/kotlin/runtime/net/url/QueryParameters$Companion; + public synthetic fun (Ljava/util/Map;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun clear ()V public fun compute (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; @@ -835,6 +835,7 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters : java/u public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public final fun get (Ljava/lang/Object;)Ljava/util/List; public fun getEntries ()Ljava/util/Set; + public final fun getForceQuery ()Z public fun getKeys ()Ljava/util/Set; public fun getSize ()I public fun getValues ()Ljava/util/Collection; @@ -856,14 +857,14 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters : java/u public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z public fun replaceAll (Ljava/util/function/BiFunction;)V public final fun size ()I - public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder; + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/QueryParameters$Builder; public fun toString ()Ljava/lang/String; public final fun values ()Ljava/util/Collection; } -public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { +public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Builder : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { public fun ()V - public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; + public final fun build ()Laws/smithy/kotlin/runtime/net/url/QueryParameters; public fun clear ()V public fun containsKey (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z public final fun containsKey (Ljava/lang/Object;)Z @@ -873,7 +874,10 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder public fun get (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public final fun get (Ljava/lang/Object;)Ljava/util/List; + public final fun getDecoded ()Ljava/lang/String; + public final fun getEncoded ()Ljava/lang/String; public fun getEntries ()Ljava/util/Set; + public final fun getForceQuery ()Z public fun getKeys ()Ljava/util/Set; public fun getSize ()I public fun getValues ()Ljava/util/Collection; @@ -885,114 +889,114 @@ public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Builder public fun remove (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; public final fun remove (Ljava/lang/Object;)Ljava/util/List; + public final fun setDecoded (Ljava/lang/String;)V + public final fun setEncoded (Ljava/lang/String;)V + public final fun setForceQuery (Z)V public final fun size ()I public final fun values ()Ljava/util/Collection; } -public final class aws/smithy/kotlin/runtime/net/newnet/QueryParameters$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; - public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; - public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; +public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/QueryParameters; + public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/QueryParameters; + public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/QueryParameters; } -public final class aws/smithy/kotlin/runtime/net/newnet/Url { - public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/Url$Companion; - public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILaws/smithy/kotlin/runtime/net/newnet/UrlPath;Laws/smithy/kotlin/runtime/net/newnet/QueryParameters;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/net/newnet/UserInfo;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class aws/smithy/kotlin/runtime/net/url/Url { + public static final field Companion Laws/smithy/kotlin/runtime/net/url/Url$Companion; + public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILaws/smithy/kotlin/runtime/net/url/UrlPath;Laws/smithy/kotlin/runtime/net/url/QueryParameters;Laws/smithy/kotlin/runtime/net/url/UserInfo;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getFragment ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; - public final fun getPath ()Laws/smithy/kotlin/runtime/net/newnet/UrlPath; + public final fun getParameters ()Laws/smithy/kotlin/runtime/net/url/QueryParameters; + public final fun getPath ()Laws/smithy/kotlin/runtime/net/url/UrlPath; public final fun getPort ()I - public final fun getQueryParameters ()Laws/smithy/kotlin/runtime/net/newnet/QueryParameters; public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; - public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/newnet/UserInfo; - public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/Url$Builder; + public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/url/UserInfo; + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/Url$Builder; public fun toString ()Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/net/newnet/Url$Builder { +public final class aws/smithy/kotlin/runtime/net/url/Url$Builder { public fun ()V - public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/Url; - public final fun clearQueryParameters ()V - public final fun clearUserInfo ()V - public final fun getFragmentDecoded ()Ljava/lang/String; - public final fun getFragmentEncoded ()Ljava/lang/String; + public final fun build ()Laws/smithy/kotlin/runtime/net/url/Url; + public final fun getDecodedFragment ()Ljava/lang/String; + public final fun getEncodedFragment ()Ljava/lang/String; public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; - public final fun getPathDecoded ()Ljava/lang/String; - public final fun getPathEncoded ()Ljava/lang/String; + public final fun getParameters ()Laws/smithy/kotlin/runtime/net/url/QueryParameters$Builder; + public final fun getPath ()Laws/smithy/kotlin/runtime/net/url/UrlPath$Builder; public final fun getPort ()Ljava/lang/Integer; - public final fun getQueryParametersDecoded ()Ljava/lang/String; - public final fun getQueryParametersEncoded ()Ljava/lang/String; public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; + public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/url/UserInfo$Builder; + public final fun parameters (Lkotlin/jvm/functions/Function1;)V public final fun path (Lkotlin/jvm/functions/Function1;)V - public final fun queryParameters (Lkotlin/jvm/functions/Function1;)V - public final fun setFragmentDecoded (Ljava/lang/String;)V - public final fun setFragmentEncoded (Ljava/lang/String;)V + public final fun setDecodedFragment (Ljava/lang/String;)V + public final fun setEncodedFragment (Ljava/lang/String;)V public final fun setHost (Laws/smithy/kotlin/runtime/net/Host;)V - public final fun setPathDecoded (Ljava/lang/String;)V - public final fun setPathEncoded (Ljava/lang/String;)V public final fun setPort (Ljava/lang/Integer;)V - public final fun setQueryParametersDecoded (Ljava/lang/String;)V - public final fun setQueryParametersEncoded (Ljava/lang/String;)V public final fun setScheme (Laws/smithy/kotlin/runtime/net/Scheme;)V public final fun userInfo (Lkotlin/jvm/functions/Function1;)V } -public final class aws/smithy/kotlin/runtime/net/newnet/Url$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/Url; - public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/Url; +public final class aws/smithy/kotlin/runtime/net/url/Url$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/Url; + public final fun parse (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/Url; } -public final class aws/smithy/kotlin/runtime/net/newnet/UrlPath { - public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/UrlPath$Companion; +public final class aws/smithy/kotlin/runtime/net/url/UrlPath { + public static final field Companion Laws/smithy/kotlin/runtime/net/url/UrlPath$Companion; public synthetic fun (Ljava/util/List;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getSegments ()Ljava/util/List; public final fun getTrailingSlash ()Z - public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/UrlPath$Builder; + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/UrlPath$Builder; public fun toString ()Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/net/newnet/UrlPath$Builder { +public final class aws/smithy/kotlin/runtime/net/url/UrlPath$Builder { public fun ()V - public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/UrlPath; - public final fun clearSegments ()V + public final fun build ()Laws/smithy/kotlin/runtime/net/url/UrlPath; + public final fun getDecoded ()Ljava/lang/String; public final fun getDecodedSegments ()Ljava/util/List; + public final fun getEncoded ()Ljava/lang/String; public final fun getEncodedSegments ()Ljava/util/List; public final fun getTrailingSlash ()Z + public final fun setDecoded (Ljava/lang/String;)V + public final fun setEncoded (Ljava/lang/String;)V public final fun setTrailingSlash (Z)V } -public final class aws/smithy/kotlin/runtime/net/newnet/UrlPath$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/UrlPath; - public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UrlPath; - public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UrlPath; +public final class aws/smithy/kotlin/runtime/net/url/UrlPath$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/UrlPath; + public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/UrlPath; + public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/UrlPath; } -public final class aws/smithy/kotlin/runtime/net/newnet/UserInfo { - public static final field Companion Laws/smithy/kotlin/runtime/net/newnet/UserInfo$Companion; +public final class aws/smithy/kotlin/runtime/net/url/UserInfo { + public static final field Companion Laws/smithy/kotlin/runtime/net/url/UserInfo$Companion; public synthetic fun (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getPassword ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public final fun getUserName ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; - public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/newnet/UserInfo$Builder; + public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/UserInfo$Builder; public fun toString ()Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/net/newnet/UserInfo$Builder { +public final class aws/smithy/kotlin/runtime/net/url/UserInfo$Builder { public fun ()V - public final fun build ()Laws/smithy/kotlin/runtime/net/newnet/UserInfo; - public final fun getPasswordDecoded ()Ljava/lang/String; - public final fun getPasswordEncoded ()Ljava/lang/String; - public final fun getUserNameDecoded ()Ljava/lang/String; - public final fun getUserNameEncoded ()Ljava/lang/String; - public final fun setPasswordDecoded (Ljava/lang/String;)V - public final fun setPasswordEncoded (Ljava/lang/String;)V - public final fun setUserNameDecoded (Ljava/lang/String;)V - public final fun setUserNameEncoded (Ljava/lang/String;)V + public final fun build ()Laws/smithy/kotlin/runtime/net/url/UserInfo; + public final fun getDecodedPassword ()Ljava/lang/String; + public final fun getDecodedUserName ()Ljava/lang/String; + public final fun getEncodedPassword ()Ljava/lang/String; + public final fun getEncodedUserName ()Ljava/lang/String; + public final fun setDecodedPassword (Ljava/lang/String;)V + public final fun setDecodedUserName (Ljava/lang/String;)V + public final fun setEncodedPassword (Ljava/lang/String;)V + public final fun setEncodedUserName (Ljava/lang/String;)V } -public final class aws/smithy/kotlin/runtime/net/newnet/UserInfo$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/newnet/UserInfo; - public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UserInfo; - public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/newnet/UserInfo; +public final class aws/smithy/kotlin/runtime/net/url/UserInfo$Companion { + public final fun getEmpty ()Laws/smithy/kotlin/runtime/net/url/UserInfo; + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/UserInfo; + public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/UserInfo; + public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/UserInfo; } public final class aws/smithy/kotlin/runtime/operation/ExecutionContext : aws/smithy/kotlin/runtime/util/MutableAttributes, kotlinx/coroutines/CoroutineScope { @@ -1427,6 +1431,7 @@ public final class aws/smithy/kotlin/runtime/text/encoding/Encodable { public final fun getEncoded ()Ljava/lang/String; public final fun getEncoding ()Laws/smithy/kotlin/runtime/text/encoding/Encoding; public fun hashCode ()I + public final fun isEmpty ()Z public fun toString ()Ljava/lang/String; } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt similarity index 59% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt index 939ce3865..c650026e2 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.net.newnet +package aws.smithy.kotlin.runtime.net.url import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding @@ -10,7 +10,15 @@ import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding /** * Represents the parameters in a URL query string. */ -public class QueryParameters private constructor(private val delegate: Map>) : Map> by delegate { +public class QueryParameters private constructor( + private val delegate: Map>, + + /** + * A flag indicating whether to force inclusion of the `?` query separator even when there are no parameters (e.g., + * `http://foo.com?` vs `http://foo.com`). + */ + public val forceQuery: Boolean, +) : Map> by delegate { public companion object { /** * Create new [QueryParameters] via a DSL builder block @@ -19,13 +27,23 @@ public class QueryParameters private constructor(private val delegate: Map Unit): QueryParameters = Builder().apply(block).build() - private fun asDecoded(delegate: Map>) = asString(delegate, Encodable::decoded) - private fun asEncoded(delegate: Map>) = asString(delegate, Encodable::encoded) + private fun asDecoded(delegate: Map>, forceQuery: Boolean) = + asString(delegate, forceQuery, Encodable::decoded) + + private fun asEncoded(delegate: Map>, forceQuery: Boolean) = + asString(delegate, forceQuery, Encodable::encoded) - private fun asString(delegate: Map>, encodableForm: (Encodable) -> String) = + private fun asString( + delegate: Map>, + forceQuery: Boolean, + encodableForm: (Encodable) -> String, + ) = delegate .entries - .joinToString("&") { (key, values) -> + .joinToString( + separator = "&", + prefix = if (forceQuery || delegate.isNotEmpty()) "?" else "", + ) { (key, values) -> values.joinToString("&") { "${encodableForm(key)}=${encodableForm(it)}" } } @@ -48,9 +66,12 @@ public class QueryParameters private constructor(private val delegate: Map v.toMutableList() }) + public fun toBuilder(): Builder = Builder( + delegate.mapValuesTo(mutableMapOf()) { (_, v) -> v.toMutableList() }, + forceQuery, + ) - override fun toString(): String = asEncoded(delegate) + override fun toString(): String = asEncoded(delegate, forceQuery) // TODO dsl functions for access to encoded/decoded param maps, likely requires some kind of MutableMapView similar // to MutableListView @@ -59,22 +80,43 @@ public class QueryParameters private constructor(private val delegate: Map>, + + /** + * A flag indicating whether to force inclusion of the `?` query separator even when there are no parameters + * (e.g., `http://foo.com?` vs `http://foo.com`). + */ + public var forceQuery: Boolean = false, ) : MutableMap> by delegate { /** * Initialize an empty [QueryParameters] builder */ public constructor() : this(mutableMapOf()) - internal fun asDecoded() = asDecoded(delegate) - internal fun asEncoded() = asEncoded(delegate) + /** + * Get or set the query parameters as a **decoded** string. + */ + public var decoded: String + get() = asDecoded(delegate, forceQuery) + set(value) { parseDecoded(value) } + + /** + * Get or set the query parameters as an **encoded** string. + */ + public var encoded: String + get() = asEncoded(delegate, forceQuery) + set(value) { parseEncoded(value) } internal fun parseDecoded(decoded: String) = parse(decoded, PercentEncoding.Query::encodableFromDecoded) internal fun parseEncoded(encoded: String) = parse(encoded, PercentEncoding.Query::encodableFromEncoded) private fun parse(text: String, toEncodable: (String) -> Encodable) { clear() - if (text.isNotEmpty()) { - text.split("&").forEach { segment -> + + forceQuery = text == "?" + val params = text.removePrefix("?") + + if (params.isNotEmpty()) { + params.split("&").forEach { segment -> val parts = segment.split("=") val key = parts[0] val value = when (parts.size) { @@ -91,6 +133,9 @@ public class QueryParameters private constructor(private val delegate: Map v.toList() }.toMap()) + public fun build(): QueryParameters = QueryParameters( + delegate.mapValues { (_, v) -> v.toList() }.toMap(), + forceQuery, + ) } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt similarity index 65% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt index 4a1aa2112..7bf552333 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt @@ -2,15 +2,16 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.net.newnet +package aws.smithy.kotlin.runtime.net.url import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.net.splitHostPort +import aws.smithy.kotlin.runtime.net.isIpv6 import aws.smithy.kotlin.runtime.net.toUrlString import aws.smithy.kotlin.runtime.text.Scanner import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding +import aws.smithy.kotlin.runtime.text.urlDecodeComponent /** * Represents a full, valid URL @@ -18,8 +19,7 @@ import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding * @param host The [Host] for the URL * @param port The remote port number for the URL (e.g., TCP port) * @param path The path element of this URL - * @param queryParameters Optional query parameters for this URL. Note that `null` parameters are different from *empty* - * parameters. + * @param parameters The query parameters for this URL. * @param fragment Optional fragment component of this URL (without the `#` character) * @param userInfo Optional user authentication information for this URL */ @@ -28,9 +28,9 @@ public class Url private constructor( public val host: Host, public val port: Int, public val path: UrlPath, - public val queryParameters: QueryParameters?, + public val parameters: QueryParameters, + public val userInfo: UserInfo, public val fragment: Encodable?, - public val userInfo: UserInfo?, ) { public companion object { /** @@ -45,7 +45,7 @@ public class Url private constructor( * @param encoded An encoded URL string * @return A new [Url] instance */ - public fun parseEncoded(encoded: String): Url = Url { + public fun parse(encoded: String): Url = Url { val scanner = Scanner(encoded) scanner.requireAndSkip("://") { scheme = Scheme.parse(it) } @@ -67,12 +67,12 @@ public class Url private constructor( scanner.ifStartsWith("?") { scanner.upToOrEnd("#") { - queryParameters { parseEncoded(it) } + parameters { parseEncoded(it) } } } scanner.ifStartsWith("#") { - scanner.upToOrEnd { fragmentEncoded = it } + scanner.upToOrEnd { encodedFragment = it } } } } @@ -99,10 +99,7 @@ public class Url private constructor( append(port) } append(path) - queryParameters?.let { - append('?') - append(it) - } + append(parameters) fragment?.let { append('#') append(it.encoded) @@ -137,7 +134,10 @@ public class Url private constructor( // Path - private val path: UrlPath.Builder = url?.path?.toBuilder() ?: UrlPath.Builder() + /** + * Gets the URL path builder + */ + public val path: UrlPath.Builder = url?.path?.toBuilder() ?: UrlPath.Builder() /** * Update the [UrlPath] of this URL via a DSL builder block @@ -147,65 +147,35 @@ public class Url private constructor( path.apply(block) } - /** - * Get or set the URL path as a **decoded** string - */ - public var pathDecoded: String - get() = path.asDecoded() - set(value) { path.parseDecoded(value) } - - /** - * Get or set the URL path as an **encoded** string - */ - public var pathEncoded: String - get() = path.asEncoded() - set(value) { path.parseEncoded(value) } - // Query parameters - private var queryParameters = url?.queryParameters?.toBuilder() - /** - * Remove all query parameters from this URL + * Gets the query parameters builder */ - public fun clearQueryParameters() { - queryParameters = null - } + public val parameters: QueryParameters.Builder = url?.parameters?.toBuilder() ?: QueryParameters.Builder() /** * Update the [QueryParameters] of this URL via a DSL builder block * @param block The code to apply to the [QueryParameters] builder */ - public fun queryParameters(block: QueryParameters.Builder.() -> Unit) { - val queryParameters = this.queryParameters ?: QueryParameters.Builder().also { this.queryParameters = it } - queryParameters.apply(block) + public fun parameters(block: QueryParameters.Builder.() -> Unit) { + parameters.apply(block) } + // User info + /** - * Get or set the query parameters as a **decoded** string + * Get the user info builder */ - public var queryParametersDecoded: String? - get() = queryParameters?.asDecoded() - set(value) { - if (value == null) { - queryParameters = null - } else { - queryParameters { parseDecoded(value) } - } - } + public val userInfo: UserInfo.Builder = url?.userInfo?.toBuilder() ?: UserInfo.Builder() /** - * Get or set the query parameters as an **encoded** string + * Set the user info in this URL via a DSL builder block + * @param block The code to apply to the [UserInfo] builder */ - public var queryParametersEncoded: String? - get() = queryParameters?.asEncoded() - set(value) { - if (value == null) { - queryParameters = null - } else { - queryParameters { parseEncoded(value) } - } - } + public fun userInfo(block: UserInfo.Builder.() -> Unit) { + userInfo.apply(block) + } // Fragment @@ -214,37 +184,17 @@ public class Url private constructor( /** * Get or set the fragment as a **decoded** string */ - public var fragmentDecoded: String? + public var decodedFragment: String? get() = fragment?.decoded set(value) { fragment = value?.let(PercentEncoding.Fragment::encodableFromDecoded) } /** * Get or set the fragment as an **encoded** string */ - public var fragmentEncoded: String? + public var encodedFragment: String? get() = fragment?.encoded set(value) { fragment = value?.let(PercentEncoding.Fragment::encodableFromEncoded) } - // User info - - private var userInfo: UserInfo.Builder? = url?.userInfo?.toBuilder() - - /** - * Remove the user info from the URL - */ - public fun clearUserInfo() { - userInfo = null - } - - /** - * Set the user info in this URL via a DSL builder block - * @param block The code to apply to the [UserInfo] builder - */ - public fun userInfo(block: UserInfo.Builder.() -> Unit) { - val userInfo = this.userInfo ?: UserInfo.Builder().also { this.userInfo = it } - userInfo.apply(block) - } - // Build method /** @@ -256,9 +206,40 @@ public class Url private constructor( host, port ?: scheme.defaultPort, path.build(), - queryParameters?.build(), + parameters.build(), + userInfo.build(), fragment, - userInfo?.build(), ) } } + +private fun String.splitHostPort(): Pair { + val lBracketIndex = indexOf('[') + val rBracketIndex = indexOf(']') + val lastColonIndex = lastIndexOf(":") + val hostEndIndex = when { + rBracketIndex != -1 -> rBracketIndex + 1 + lastColonIndex != -1 -> lastColonIndex + else -> length + } + + require(lBracketIndex == -1 && rBracketIndex == -1 || lBracketIndex < rBracketIndex) { "unmatched [ or ]" } + require(lBracketIndex <= 0) { "unexpected characters before [" } + require(rBracketIndex == -1 || rBracketIndex == hostEndIndex - 1) { "unexpected characters after ]" } + + val host = if (lBracketIndex != -1) { + substring(lBracketIndex + 1 until rBracketIndex) + } else { + substring(0 until hostEndIndex) + } + + val decodedHost = host.urlDecodeComponent() + if (lBracketIndex != -1 && rBracketIndex != -1 && !decodedHost.isIpv6()) { + throw IllegalArgumentException("non-ipv6 host was enclosed in []-brackets") + } + + return Pair( + Host.parse(decodedHost), + if (hostEndIndex != -1 && hostEndIndex != length) substring(hostEndIndex + 1).toInt() else null, + ) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt similarity index 85% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt index a9f39ae17..20b9e600e 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UrlPath.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.net.newnet +package aws.smithy.kotlin.runtime.net.url import aws.smithy.kotlin.runtime.collections.MutableListView import aws.smithy.kotlin.runtime.text.encoding.Encodable @@ -13,7 +13,10 @@ import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding * @param segments A list of path segments * @param trailingSlash Indicates whether a trailing slash is present in the path (e.g., "/foo/bar/" vs "/foo/bar") */ -public class UrlPath private constructor(public val segments: List, public val trailingSlash: Boolean = false) { +public class UrlPath private constructor( + public val segments: List, + public val trailingSlash: Boolean = false, +) { public companion object { /** * Create a new [UrlPath] via a DSL builder block @@ -28,15 +31,13 @@ public class UrlPath private constructor(public val segments: List, p private fun asEncoded(segments: List, trailingSlash: Boolean) = asString(segments, trailingSlash, Encodable::encoded) - private fun asString(segments: List, trailingSlash: Boolean, encodableForm: (Encodable) -> String) = when { - segments.isEmpty() -> if (trailingSlash) "/" else "" - else -> segments.joinToString( + private fun asString(segments: List, trailingSlash: Boolean, encodableForm: (Encodable) -> String) = + segments.joinToString( separator = "/", - prefix = "/", + prefix = if (segments.isEmpty()) "" else "/", postfix = if (trailingSlash) "/" else "", transform = encodableForm, ) - } /** * Parse a **decoded** path string into a [UrlPath] instance @@ -73,11 +74,18 @@ public class UrlPath private constructor(public val segments: List, p private val segments: MutableList = path?.segments?.toMutableList() ?: mutableListOf() /** - * Remove all existing segments + * Get or set the URL path as a **decoded** string. */ - public fun clearSegments() { - segments.clear() - } + public var decoded: String + get() = asDecoded(segments, trailingSlash) + set(value) { parseDecoded(value) } + + /** + * Get or set the URL path as an **encoded** string. + */ + public var encoded: String + get() = asEncoded(segments, trailingSlash) + set(value) { parseEncoded(value) } /** * A mutable list of **decoded** path segments. Any changes to this list will update the builder. @@ -102,9 +110,6 @@ public class UrlPath private constructor(public val segments: List, p */ public var trailingSlash: Boolean = path?.trailingSlash ?: false - internal fun asDecoded() = asDecoded(segments, trailingSlash) - internal fun asEncoded() = asEncoded(segments, trailingSlash) - internal fun parseDecoded(decoded: String): Unit = parse(decoded, PercentEncoding.Path::encodableFromDecoded) internal fun parseEncoded(encoded: String): Unit = parse(encoded, PercentEncoding.Path::encodableFromEncoded) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt similarity index 80% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt index ac0f1811d..400fb9e36 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/newnet/UserInfo.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.net.newnet +package aws.smithy.kotlin.runtime.net.url import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding @@ -14,6 +14,11 @@ import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding */ public class UserInfo private constructor(public val userName: Encodable, public val password: Encodable) { public companion object { + /** + * No username or password + */ + public val Empty: UserInfo = UserInfo(Encodable.Empty, Encodable.Empty) + /** * Create a new [UserInfo] via a DSL builder block * @param block The code to apply to the builder @@ -42,7 +47,11 @@ public class UserInfo private constructor(public val userName: Encodable, public */ public fun toBuilder(): Builder = Builder(this) - override fun toString(): String = "${userName.encoded}:${password.encoded}" + override fun toString(): String = when { + userName.isEmpty -> "" + password.isEmpty -> userName.encoded + else -> "${userName.encoded}:${userName.decoded}" + } /** * A mutable builder used to construct [UserInfo] instances @@ -55,21 +64,33 @@ public class UserInfo private constructor(public val userName: Encodable, public private var userName = userInfo?.userName ?: Encodable.Empty - public var userNameDecoded: String + /** + * Gets or sets the username as a **decoded** string + */ + public var decodedUserName: String get() = userName.decoded set(value) { userName = PercentEncoding.UserInfo.encodableFromDecoded(value) } - public var userNameEncoded: String + /** + * Gets or sets the username as an **encoded** string + */ + public var encodedUserName: String get() = userName.encoded set(value) { userName = PercentEncoding.UserInfo.encodableFromDecoded(value) } private var password = userInfo?.password ?: Encodable.Empty - public var passwordDecoded: String + /** + * Gets or sets the password as a **decoded** string + */ + public var decodedPassword: String get() = password.decoded set(value) { password = PercentEncoding.UserInfo.encodableFromDecoded(value) } - public var passwordEncoded: String + /** + * Gets or sets the password as an **encoded** string + */ + public var encodedPassword: String get() = password.encoded set(value) { password = PercentEncoding.UserInfo.encodableFromEncoded(value) } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt index 318ee51da..192e569a6 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt @@ -13,15 +13,7 @@ public class Encodable internal constructor( public val Empty: Encodable = Encodable("", "", Encoding.None) } - override fun toString(): String = buildString { - append("Encodable(decoded=") - append(decoded) - append(", encoded=") - append(encoded) - append(", encoding=") - append(encoding.name) - append(")") - } + public val isEmpty: Boolean = decoded.isEmpty() && encoded.isEmpty() override fun equals(other: Any?): Boolean { if (this === other) return true @@ -38,4 +30,14 @@ public class Encodable internal constructor( result = 31 * result + encoding.hashCode() return result } + + override fun toString(): String = buildString { + append("Encodable(decoded=") + append(decoded) + append(", encoded=") + append(encoded) + append(", encoding=") + append(encoding.name) + append(")") + } } diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/newnet/UrlTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt similarity index 96% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/newnet/UrlTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt index 8ff321f20..25e3e7b7f 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/newnet/UrlTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt @@ -2,18 +2,18 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.net.newnet +package aws.smithy.kotlin.runtime.net.url import kotlin.test.Test import kotlin.test.fail import aws.smithy.kotlin.runtime.net.Url as OldUrl -import aws.smithy.kotlin.runtime.net.newnet.Url as NewUrl +import aws.smithy.kotlin.runtime.net.url.Url as NewUrl class UrlTest { private fun testEquivalence(vararg urls: String) { val errors = urls.mapNotNull { url -> val old = OldUrl.parse(url).toString() - val new = NewUrl.parseEncoded(url).toString() + val new = NewUrl.parse(url).toString() if (old == new) null else "Old: <$old>, New: <$new>" } if (errors.isNotEmpty()) { From 8460755183649b9694d4e49f846477d807e26248 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Fri, 17 Nov 2023 02:47:06 +0000 Subject: [PATCH 05/17] Switch to new Url classes and fix compilation errors/tests --- .../kotlin/codegen/core/RuntimeTypes.kt | 12 +- .../DefaultEndpointProviderGenerator.kt | 4 +- .../DefaultEndpointProviderTestGenerator.kt | 7 +- .../protocol/HttpBindingProtocolGenerator.kt | 92 +++--- .../protocol/HttpStringValuesMapSerializer.kt | 60 ++-- .../codegen/IdempotentTokenGeneratorTest.kt | 10 +- .../DefaultEndpointProviderGeneratorTest.kt | 6 +- .../HttpBindingProtocolGeneratorTest.kt | 84 +++-- .../HttpStringValuesMapSerializerTest.kt | 8 +- ...ttp-binding-protocol-generator-test.smithy | 2 +- .../runtime/auth/awssigning/Presigner.kt | 17 +- .../runtime/auth/awssigning/PresignerTest.kt | 6 +- .../runtime/auth/awssigning/Canonicalizer.kt | 76 +++-- .../runtime/auth/awssigning/RequestMutator.kt | 14 +- .../awssigning/DefaultCanonicalizerTest.kt | 43 ++- .../awssigning/DefaultRequestMutatorTest.kt | 11 +- .../awssigning/tests/BasicSigningTestBase.kt | 8 +- .../tests/SigningSuiteTestBaseJVM.kt | 38 +-- .../kotlin/runtime/http/auth/AwsHttpSigner.kt | 6 +- .../http/auth/AwsHttpSignerTestBase.kt | 2 +- .../src/aws/smithy/kotlin/runtime/crt/Http.kt | 18 +- .../aws/smithy/kotlin/runtime/crt/HttpTest.kt | 30 +- .../http/engine/crt/ConnectionManager.kt | 9 +- .../runtime/http/engine/crt/RequestUtil.kt | 7 +- .../http/engine/crt/AsyncStressTest.kt | 2 +- .../http/engine/crt/RequestConversionTest.kt | 2 +- .../runtime/http/engine/okhttp/OkHttpUtils.kt | 28 +- .../http/engine/okhttp/OkHttpRequestTest.kt | 15 +- .../http/engine/okhttp/OkHttpResponseTest.kt | 2 +- .../http/test/util/AbstractEngineTest.kt | 2 +- .../runtime/http/test/AsyncStressTest.kt | 4 +- .../kotlin/runtime/http/test/DownloadTest.kt | 8 +- .../kotlin/runtime/http/test/RedirectTest.kt | 2 +- .../kotlin/runtime/http/test/UploadTest.kt | 12 +- .../http/test/FileUploadDownloadTest.kt | 4 +- .../kotlin/runtime/http/test/ProxyTest.kt | 5 +- .../protocol/http-client/api/http-client.api | 12 +- .../http/engine/EnvironmentProxySelector.kt | 13 +- .../kotlin/runtime/http/engine/ProxyConfig.kt | 2 +- .../runtime/http/engine/ProxySelector.kt | 2 +- .../http/operation/OperationEndpoint.kt | 26 +- .../engine/EnvironmentProxySelectorTest.kt | 2 +- .../runtime/http/engine/NoProxyHostTest.kt | 2 +- .../interceptors/ContinueInterceptorTest.kt | 2 +- .../http/operation/OperationEndpointTest.kt | 26 +- .../runtime/httptest/HttpTrafficParser.kt | 2 +- .../runtime/httptest/TestConnectionTest.kt | 24 +- runtime/protocol/http/api/http.api | 10 +- .../runtime/http/request/HttpRequest.kt | 2 +- .../http/request/HttpRequestBuilder.kt | 23 +- .../runtime/http/HttpRequestBuilderTest.kt | 21 +- runtime/runtime-core/api/runtime-core.api | 311 ++++++++---------- .../kotlin/runtime/collections/Entry.kt | 7 + .../kotlin/runtime/collections/MultiMap.kt | 41 +++ .../collections/MutableIteratorView.kt | 30 -- .../collections/MutableListIteratorView.kt | 48 --- .../runtime/collections/MutableListView.kt | 77 ----- .../runtime/collections/MutableMultiMap.kt | 82 +++++ .../collections/views/CollectionView.kt | 20 ++ .../runtime/collections/views/Converters.kt | 80 +++++ .../runtime/collections/views/EntryView.kt | 17 + .../runtime/collections/views/IterableView.kt | 12 + .../runtime/collections/views/IteratorView.kt | 14 + .../collections/views/ListIteratorView.kt | 18 + .../runtime/collections/views/ListView.kt | 24 ++ .../runtime/collections/views/MapView.kt | 36 ++ .../runtime/collections/views/MultiMapView.kt | 45 +++ .../views/MutableCollectionView.kt | 27 ++ .../collections/views/MutableEntryView.kt | 14 + .../collections/views/MutableIterableView.kt | 12 + .../collections/views/MutableIteratorView.kt | 14 + .../views/MutableListIteratorView.kt | 23 ++ .../collections/views/MutableListView.kt | 46 +++ .../collections/views/MutableMapView.kt | 46 +++ .../collections/views/MutableMultiMapView.kt | 93 ++++++ .../collections/views/MutableSetView.kt | 27 ++ .../runtime/collections/views/SetView.kt | 11 + .../kotlin/runtime/net/QueryParameters.kt | 2 + .../src/aws/smithy/kotlin/runtime/net/Url.kt | 2 + .../smithy/kotlin/runtime/net/UrlDecoding.kt | 61 ---- .../smithy/kotlin/runtime/net/UrlParser.kt | 2 + .../kotlin/runtime/net/url/QueryParameters.kt | 158 ++++++--- .../aws/smithy/kotlin/runtime/net/url/Url.kt | 155 +++++++-- .../kotlin/runtime/net/url/UrlEncoding.kt | 61 ++++ .../smithy/kotlin/runtime/net/url/UrlPath.kt | 87 ++++- .../smithy/kotlin/runtime/net/url/UserInfo.kt | 39 ++- .../aws/smithy/kotlin/runtime/text/Scanner.kt | 30 +- .../aws/smithy/kotlin/runtime/text/Text.kt | 3 + .../kotlin/runtime/text/encoding/Encodable.kt | 3 + .../kotlin/runtime/text/encoding/Encoding.kt | 4 +- .../runtime/text/encoding/PercentEncoding.kt | 10 +- .../util/newcoll/MutableIteratorView.kt | 30 -- .../util/newcoll/MutableListIteratorView.kt | 45 --- .../runtime/util/newcoll/MutableListView.kt | 77 ----- .../kotlin/runtime/net/QueryParametersTest.kt | 2 + .../kotlin/runtime/net/UrlDecodingTest.kt | 35 -- .../aws/smithy/kotlin/runtime/net/UrlTest.kt | 2 + .../runtime/net/url/QueryParametersTest.kt | 17 + .../kotlin/runtime/net/url/UrlEncodingTest.kt | 35 ++ .../UrlParsingTest.kt} | 148 +++++---- .../kotlin/runtime/net/url/UrlPathTest.kt | 55 ++++ .../smithy/kotlin/runtime/net/url/UrlTest.kt | 2 + .../smithy/kotlin/runtime/text/ScannerTest.kt | 46 +++ .../text/encoding/PercentEncodingTest.kt | 14 + runtime/smithy-client/api/smithy-client.api | 12 +- .../runtime/client/endpoints/Endpoint.kt | 2 +- .../client/endpoints/functions/Functions.kt | 10 +- .../runtime/smithy/test/HttpRequestTest.kt | 18 +- .../smithy/test/HttpRequestTestBuilderTest.kt | 87 +++-- .../auth/signing/AwsSignerBenchmark.kt | 2 +- .../benchmarks/http/HttpEngineBenchmarks.kt | 8 +- 111 files changed, 2098 insertions(+), 1166 deletions(-) create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/Converters.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/EntryView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IterableView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListIteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MapView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MultiMapView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableCollectionView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableEntryView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIterableView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListIteratorView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMapView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableSetView.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/SetView.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlDecoding.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlEncoding.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt delete mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlDecodingTest.kt create mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt create mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlEncodingTest.kt rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/{UrlParserTest.kt => url/UrlParsingTest.kt} (68%) create mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlPathTest.kt create mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/ScannerTest.kt diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 9a1bfd51b..cef9229cd 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -173,13 +173,11 @@ object RuntimeTypes { object Net : RuntimeTypePackage(KotlinDependency.CORE, "net") { val Host = symbol("Host") - val parameters = symbol("parameters") - val QueryParameters = symbol("QueryParameters") - val QueryParametersBuilder = symbol("QueryParametersBuilder") - val splitAsQueryParameters = symbol("splitAsQueryParameters") - val toQueryParameters = symbol("toQueryParameters") - val Url = symbol("Url") - val UrlDecoding = symbol("UrlDecoding") + + object Url : RuntimeTypePackage(KotlinDependency.CORE, "net.url") { + val Url = symbol("Url") + val UrlDecoding = symbol("UrlDecoding") + } } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt index f9e7beece..1e1e764d6 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt @@ -151,9 +151,9 @@ class DefaultEndpointProviderGenerator( private fun renderEndpointRule(rule: EndpointRule) { withConditions(rule.conditions) { writer.withBlock("return #T(", ")", RuntimeTypes.SmithyClient.Endpoints.Endpoint) { - writeInline("#T.parse(", RuntimeTypes.Core.Net.Url) + writeInline("#T.parse(", RuntimeTypes.Core.Net.Url.Url) renderExpression(rule.endpoint.url) - write(", #1T.DecodeAll - #1T.DecodePath),", RuntimeTypes.Core.Net.UrlDecoding) + write("),") if (rule.endpoint.headers.isNotEmpty()) { withBlock("headers = #T {", "},", RuntimeTypes.Http.Headers) { diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt index 0137dbc29..e687b97d3 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt @@ -106,12 +106,7 @@ class DefaultEndpointProviderTestGenerator( } writer.withBlock("val expected = #T(", ")", RuntimeTypes.SmithyClient.Endpoints.Endpoint) { - write( - "uri = #1T.parse(#2S, #3T.DecodeAll - #3T.DecodePath),", - RuntimeTypes.Core.Net.Url, - endpoint.url, - RuntimeTypes.Core.Net.UrlDecoding, - ) + write("uri = #T.parse(#S),", RuntimeTypes.Core.Net.Url.Url, endpoint.url) if (endpoint.headers.isNotEmpty()) { withBlock("headers = #T {", "},", RuntimeTypes.Http.Headers) { diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index 53b4c7478..ff03f9f87 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -303,46 +303,47 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { renderNonBlankGuard(ctx, binding.member, writer) } - writer.openBlock("val pathSegments = listOf(", ")") { + writer.withBlock("path.decodedSegments {", "}") { httpTrait.uri.segments.forEach { segment -> if (segment.isLabel || segment.isGreedyLabel) { // spec dictates member name and label name MUST be the same val binding = pathBindings.find { binding -> binding.memberName == segment.content - } ?: throw CodegenException("failed to find corresponding member for httpLabel `${segment.content}") + } + ?: throw CodegenException("failed to find corresponding member for httpLabel `${segment.content}") // shape must be string, number, boolean, or timestamp val targetShape = ctx.model.expectShape(binding.member.target) val memberSymbol = ctx.symbolProvider.toSymbol(binding.member) val identifier = if (targetShape.isTimestampShape) { - writer.addImport(RuntimeTypes.Core.TimestampFormat) + addImport(RuntimeTypes.Core.TimestampFormat) val tsFormat = resolver.determineTimestampFormat( binding.member, HttpBinding.Location.LABEL, defaultTimestampFormat, ) val nullCheck = if (memberSymbol.isNullable) "?" else "" - val tsLabel = formatInstant("input.${binding.member.defaultName()}$nullCheck", tsFormat, forceString = true) + val tsLabel = formatInstant( + "input.${binding.member.defaultName()}$nullCheck", + tsFormat, + forceString = true + ) tsLabel } else { "input.${binding.member.defaultName()}" } - val encodeSymbol = RuntimeTypes.Http.Util.encodeLabel - val encodeFn = if (segment.isGreedyLabel) { - writer.format("#T(greedy = true)", encodeSymbol) + if (segment.isGreedyLabel) { + write("addAll(#S.split(#S))", "\${$identifier}", '"') } else { - writer.format("#T()", encodeSymbol) + write("add(#S)", "\${$identifier}") } - writer.write("#S.$encodeFn,", "\${$identifier}") } else { // literal - writer.write("\"#L\",", segment.content.toEscapedLiteral()) + writer.write("add(\"#L\")", segment.content.toEscapedLiteral()) } } } - - writer.write("""path = pathSegments.joinToString(separator = "/", prefix = "/")""") } else { // all literals, inline directly val resolvedPath = httpTrait.uri.segments.joinToString( @@ -352,7 +353,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { it.content.toEscapedLiteral() }, ) - writer.write("path = \"#L\"", resolvedPath) + writer.write("path.encoded = \"#L\"", resolvedPath) } } @@ -373,45 +374,44 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { if (queryBindings.isEmpty() && queryLiterals.isEmpty() && queryMapBindings.isEmpty()) return - writer - .withBlock("#T {", "}", RuntimeTypes.Core.Net.parameters) { - queryLiterals.forEach { (key, value) -> - writer.write("append(#S, #S)", key, value) - } - - // render length check if applicable - queryBindings.forEach { binding -> renderNonBlankGuard(ctx, binding.member, writer) } + writer.withBlock("parameters.encodedParameters {", "}") { + queryLiterals.forEach { (key, value) -> + writer.write("add(#S, #S)", key, value) + } - renderStringValuesMapParameters(ctx, queryBindings, writer) + // render length check if applicable + queryBindings.forEach { binding -> renderNonBlankGuard(ctx, binding.member, writer) } - queryMapBindings.forEach { - // either Map or Map> - // https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#httpqueryparams-trait - val target = ctx.model.expectShape(it.member.target) - val valueTarget = ctx.model.expectShape(target.value.target) - val fn = when (valueTarget.type) { - ShapeType.STRING -> "append" - ShapeType.LIST, ShapeType.SET -> "appendAll" - else -> throw CodegenException("unexpected value type for httpQueryParams map") - } + renderStringValuesMapParameters(ctx, queryBindings, writer) - val nullCheck = if (target.hasTrait()) { - "if (value != null) " - } else { - "" - } + queryMapBindings.forEach { + // either Map or Map> + // https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#httpqueryparams-trait + val target = ctx.model.expectShape(it.member.target) + val valueTarget = ctx.model.expectShape(target.value.target) + val fn = when (valueTarget.type) { + ShapeType.STRING -> "add" + ShapeType.LIST, ShapeType.SET -> "addAll" + else -> throw CodegenException("unexpected value type for httpQueryParams map") + } - writer.write("input.${it.member.defaultName()}") - .indent() - // ensure query precedence rules are enforced by filtering keys already set - // (httpQuery bound members take precedence over a query map with same key) - .write("?.filterNot{ contains(it.key) }") - .withBlock("?.forEach { (key, value) ->", "}") { - write("${nullCheck}$fn(key, value)") - } - .dedent() + val nullCheck = if (target.hasTrait()) { + "if (value != null) " + } else { + "" } + + writer.write("input.${it.member.defaultName()}") + .indent() + // ensure query precedence rules are enforced by filtering keys already set + // (httpQuery bound members take precedence over a query map with same key) + .write("?.filterNot{ contains(it.key) }") + .withBlock("?.forEach { (key, value) ->", "}") { + write("${nullCheck}$fn(key, value)") + } + .dedent() } + } } // shared implementation for rendering members that belong to StringValuesMap (e.g. Header or Query parameters) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt index b9793c3be..c82be5333 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt @@ -21,14 +21,16 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.utils.AbstractCodeWriter /** - * Shared implementation to generate serialization for members bound to HTTP query parameters or headers - * (both of which are implemented using `ValuesMap`). + * Shared implementation to generate serialization for members bound to HTTP query parameters or headers. These + * locations are represented by different data structures: + * * **query parameters**: `MutableMultiMap` + * * **headers**: `ValuesMapBuilder` * * This is a partial generator, the entry point for rendering from this component is an open block where the current - * value of `this` is a `ValuesMapBuilder`. + * value of `this` one of the types listed above. * * Example output this class generates: - * ``` + * ```kotlin * if (input.field1 != null) append("X-Foo", input.field1) * if (input.field2?.isNotEmpty() == true) appendAll("X-Foo", input.field2!!.map { it.value }) * ``` @@ -56,6 +58,7 @@ class HttpStringValuesMapSerializer( val memberTarget = model.expectShape(it.member.target) val paramName = it.locationName val location = it.location + val addFnName = location.addFnName val member = it.member val memberSymbol = symbolProvider.toSymbol(member) when (memberTarget) { @@ -64,43 +67,45 @@ class HttpStringValuesMapSerializer( val tsFormat = resolver.determineTimestampFormat(member, location, defaultTimestampFormat) // headers/query params need to be a string val formatted = formatInstant("input.$memberName", tsFormat, forceString = true) - val appendFn = writer.format("append(#S, #L)", paramName, formatted) + val addFn = writer.format("#L(#S, #L)", addFnName, paramName, formatted) writer.addImport(RuntimeTypes.Core.TimestampFormat) - writer.writeWithCondIfCheck(memberSymbol.isNullable, "input.$memberName != null", appendFn) + writer.writeWithCondIfCheck(memberSymbol.isNullable, "input.$memberName != null", addFn) } is BlobShape -> { - val appendFn = writer.format( - "append(#S, input.#L.#T()", + val addFn = writer.format( + "#L(#S, input.#L.#T())", + addFnName, paramName, memberName, RuntimeTypes.Core.Text.Encoding.encodeBase64String, ) - writer.writeWithCondIfCheck(memberSymbol.isNullable, "input.$memberName?.isNotEmpty() == true", appendFn) + writer.writeWithCondIfCheck(memberSymbol.isNullable, "input.$memberName?.isNotEmpty() == true", addFn) } is StringShape -> renderStringShape(it, memberTarget, writer) is IntEnumShape -> { - val appendFn = writer.format("append(#S, \"\${input.#L.value}\")", paramName, memberName) + val addFn = writer.format("#L(#S, \"\${input.#L.value}\")", addFnName, paramName, memberName) if (memberSymbol.isNullable) { - writer.write("if (input.$memberName != null) $appendFn") + writer.write("if (input.$memberName != null) $addFn") } else { val defaultCheck = defaultCheck(member) ?: "" - writer.writeWithCondIfCheck(defaultCheck.isNotEmpty(), defaultCheck, appendFn) + writer.writeWithCondIfCheck(defaultCheck.isNotEmpty(), defaultCheck, addFn) } } else -> { // encode to string val encodedValue = "\"\${input.$memberName}\"" - val appendFn = writer.format("append(#S, #L)", paramName, encodedValue) + val addFn = writer.format("#L(#S, #L)", addFnName, paramName, encodedValue) if (memberSymbol.isNullable) { - writer.write("if (input.$memberName != null) $appendFn") + writer.write("if (input.$memberName != null) $addFn") } else { val defaultCheck = defaultCheck(member) ?: "" - writer.writeWithCondIfCheck(defaultCheck.isNotEmpty(), defaultCheck, appendFn) + writer.writeWithCondIfCheck(defaultCheck.isNotEmpty(), defaultCheck, addFn) } } } } } + private fun defaultCheck(member: MemberShape): String? { val memberSymbol = symbolProvider.toSymbol(member) val memberName = symbolProvider.toMemberName(member) @@ -149,12 +154,13 @@ class HttpStringValuesMapSerializer( val memberName = symbolProvider.toMemberName(binding.member) val memberSymbol = symbolProvider.toSymbol(binding.member) val paramName = binding.locationName - // appendAll collection parameter 2 + // addAll collection parameter 2 val param2 = if (mapFnContents.isEmpty()) "input.$memberName" else "input.$memberName.map { $mapFnContents }" val nullCheck = if (memberSymbol.isNullable) "?" else "" writer.write( - "if (input.#1L$nullCheck.isNotEmpty() == true) appendAll(#2S, #3L)", + "if (input.#L$nullCheck.isNotEmpty() == true) #L(#S, #L)", memberName, + binding.location.addAllFnName, paramName, param2, ) @@ -163,6 +169,7 @@ class HttpStringValuesMapSerializer( private fun renderStringShape(binding: HttpBindingDescriptor, memberTarget: StringShape, writer: KotlinWriter) { val memberName = symbolProvider.toMemberName(binding.member) val location = binding.location + val addFnName = location.addFnName val paramName = binding.locationName val memberSymbol = symbolProvider.toSymbol(binding.member) @@ -171,7 +178,11 @@ class HttpStringValuesMapSerializer( if ((location == HttpBinding.Location.QUERY || location == HttpBinding.Location.HEADER) && binding.member.hasTrait()) { // Call the idempotency token function if no supplied value. writer.addImport(RuntimeTypes.SmithyClient.IdempotencyTokenProviderExt) - writer.write("append(#S, (input.$memberName ?: context.idempotencyTokenProvider.generateToken()))", paramName) + writer.write( + "#L(#S, (input.$memberName ?: context.idempotencyTokenProvider.generateToken()))", + addFnName, + paramName, + ) } else { val nullCheck = if (location == HttpBinding.Location.QUERY || @@ -196,8 +207,17 @@ class HttpStringValuesMapSerializer( else -> "" } - val appendFn = writer.format("append(#S, #L)", paramName, "input.${memberName}$suffix") - writer.writeWithCondIfCheck(cond.isNotEmpty(), cond, appendFn) + val addFn = writer.format("#L(#S, #L)", addFnName, paramName, "input.${memberName}$suffix") + writer.writeWithCondIfCheck(cond.isNotEmpty(), cond, addFn) } } } + +private val HttpBinding.Location.addFnName: String + get() = when (this) { + HttpBinding.Location.QUERY, HttpBinding.Location.QUERY_PARAMS -> "add" // uses MutableMultiMap + else -> "append" // uses ValuesMapBuilder + } + +private val HttpBinding.Location.addAllFnName: String + get() = "${addFnName}All" diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt index 898c1a39d..d8576b119 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt @@ -37,7 +37,7 @@ class IdempotentTokenGeneratorTest { builder.method = HttpMethod.POST builder.url { - path = "/input/AllocateWidget" + path.encoded = "/input/AllocateWidget" } val payload = serializeAllocateWidgetOperationBody(context, input) @@ -63,9 +63,9 @@ internal class AllocateWidgetQueryOperationSerializer: HttpSerialize { builder.method = HttpMethod.POST builder.url { - val pathSegments = listOf( - "smoketest", - "$label1".encodeLabel(), - "foo", - ) - path = pathSegments.joinToString(separator = "/", prefix = "/") - parameters { - if (input.query1 != null) append("Query1", input.query1) + path.decodedSegments { + add("smoketest") + addAll("$label1".split("\"")) + add("foo") + } + parameters.encodedParameters { + if (input.query1 != null) add("Query1", input.query1) } } @@ -88,7 +87,7 @@ internal class ExplicitStringOperationSerializer: HttpSerialize { builder.method = HttpMethod.POST builder.url { - path = "/input/enum" + path.encoded = "/input/enum" } builder.headers { @@ -256,15 +255,14 @@ internal class TimestampInputOperationSerializer: HttpSerialize( - "input", - "timestamp", - "$tsLabel".encodeLabel(), - ) - path = pathSegments.joinToString(separator = "/", prefix = "/") - parameters { - if (input.queryTimestamp != null) append("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) - if (input.queryTimestampList?.isNotEmpty() == true) appendAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) + path.decodedSegments { + add("input") + add("timestamp") + add("$tsLabel") + } + parameters.encodedParameters { + if (input.queryTimestamp != null) add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) + if (input.queryTimestampList?.isNotEmpty() == true) addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) } } @@ -300,7 +298,7 @@ internal class BlobInputOperationSerializer: HttpSerialize { builder.method = HttpMethod.POST builder.url { - path = "/input/blob" + path.encoded = "/input/blob" } builder.headers { @@ -333,14 +331,13 @@ internal class ConstantQueryStringOperationSerializer: HttpSerialize( - "ConstantQueryString", - "$label1".encodeLabel(), - ) - path = pathSegments.joinToString(separator = "/", prefix = "/") - parameters { - append("foo", "bar") - append("hello", "") + path.decodedSegments { + add("ConstantQueryString") + add("$label1") + } + parameters.encodedParameters { + add("foo", "bar") + add("hello", "") } } @@ -506,7 +503,7 @@ internal class SmokeTestOperationDeserializer: HttpDeserialize( - "foo", - "$label1".encodeLabel(), - "$label2".encodeLabel(), - ) - path = pathSegments.joinToString(separator = "/", prefix = "/") - parameters { + path.decodedSegments { + add("foo") + add("$label1") + add("$label2") + } + parameters.encodedParameters { require(input.garply?.isNotBlank() == true) { "garply is bound to the URI and must be a non-blank value" } - if (input.corge != null) append("corge", input.corge) - if (input.garply != null) append("garply", input.garply) - if (input.grault != null) append("grault", input.grault) - if (input.quux != null) append("quux", "$quux") + if (input.corge != null) add("corge", input.corge) + if (input.garply != null) add("garply", input.garply) + if (input.grault != null) add("grault", input.grault) + if (input.quux != null) add("quux", "$quux") } """ diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt index 93727b3db..285fa1414 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt @@ -72,7 +72,7 @@ class HttpStringValuesMapSerializerTest { contents.assertBalancedBracesAndParens() val expectedContents = """ - if (input.qInt != 0) append("q-int", "${'$'}{input.qInt}") + if (input.qInt != 0) add("q-int", "${'$'}{input.qInt}") """.trimIndent() contents.shouldContainOnlyOnceWithDiff(expectedContents) } @@ -84,7 +84,7 @@ class HttpStringValuesMapSerializerTest { contents.assertBalancedBracesAndParens() val expectedContents = """ - append("q-int", "${'$'}{input.qInt}") + add("q-int", "${'$'}{input.qInt}") """.trimIndent() contents.shouldContainOnlyOnceWithDiff(expectedContents) } @@ -190,8 +190,8 @@ class HttpStringValuesMapSerializerTest { val queryContents = getTestContents(defaultModel, "com.test#TimestampInput", HttpBinding.Location.QUERY) val expectedQueryContents = """ - if (input.queryTimestamp != null) append("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) - if (input.queryTimestampList?.isNotEmpty() == true) appendAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) + if (input.queryTimestamp != null) add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) + if (input.queryTimestampList?.isNotEmpty() == true) addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) """.trimIndent() queryContents.shouldContainOnlyOnceWithDiff(expectedQueryContents) } diff --git a/codegen/smithy-kotlin-codegen/src/test/resources/software/amazon/smithy/kotlin/codegen/http-binding-protocol-generator-test.smithy b/codegen/smithy-kotlin-codegen/src/test/resources/software/amazon/smithy/kotlin/codegen/http-binding-protocol-generator-test.smithy index 68b65d979..9af47bc59 100644 --- a/codegen/smithy-kotlin-codegen/src/test/resources/software/amazon/smithy/kotlin/codegen/http-binding-protocol-generator-test.smithy +++ b/codegen/smithy-kotlin-codegen/src/test/resources/software/amazon/smithy/kotlin/codegen/http-binding-protocol-generator-test.smithy @@ -31,7 +31,7 @@ service Test { ] } -@http(method: "POST", uri: "/smoketest/{label1}/foo") +@http(method: "POST", uri: "/smoketest/{label1+}/foo") operation SmokeTest { input: SmokeTestRequest, output: SmokeTestResponse, diff --git a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt index 882f5e915..1bcb8f092 100644 --- a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt +++ b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt @@ -13,7 +13,7 @@ import aws.smithy.kotlin.runtime.http.operation.ResolveEndpointRequest import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.header -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext @InternalApi @@ -51,14 +51,13 @@ public suspend fun presignRequest( return HttpRequest( method = signedRequest.method, - url = Url( - scheme = endpoint.uri.scheme, - host = endpoint.uri.host, - port = endpoint.uri.port, - path = signedRequest.url.path, - parameters = signedRequest.url.parameters, - encodeParameters = false, - ), + url = Url { + scheme = endpoint.uri.scheme + host = endpoint.uri.host + port = endpoint.uri.port + path.copyFrom(signedRequest.url.path) + parameters.copyFrom(signedRequest.url.parameters) + }, headers = signedRequest.headers, body = HttpBody.Empty, ) diff --git a/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt b/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt index b43f79ba9..1aec17b59 100644 --- a/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt +++ b/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt @@ -13,7 +13,7 @@ import aws.smithy.kotlin.runtime.http.operation.ResolveEndpointRequest import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.url -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.util.Attributes import kotlinx.coroutines.test.runTest @@ -61,9 +61,7 @@ class PresignerTest { assertEquals(expectedUrl.host, actualUrl.host) assertEquals(expectedUrl.port, actualUrl.port) assertEquals(expectedUrl.path, actualUrl.path) - expectedUrl.parameters.forEach { key, value -> - assertEquals(value, actualUrl.parameters.getAll(key)) - } + assertEquals(expectedUrl.parameters, actualUrl.parameters) } } diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt index 58e58c8c1..ab25388ed 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt @@ -11,15 +11,14 @@ import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.toBuilder -import aws.smithy.kotlin.runtime.http.util.encodeLabel import aws.smithy.kotlin.runtime.io.* import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers -import aws.smithy.kotlin.runtime.net.UrlBuilder +import aws.smithy.kotlin.runtime.net.url.QueryParameters +import aws.smithy.kotlin.runtime.net.url.Url +import aws.smithy.kotlin.runtime.net.url.UrlPath +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import aws.smithy.kotlin.runtime.text.encoding.encodeToHex -import aws.smithy.kotlin.runtime.text.normalizePathSegments -import aws.smithy.kotlin.runtime.text.transformPathSegments import aws.smithy.kotlin.runtime.text.urlEncodeComponent -import aws.smithy.kotlin.runtime.text.urlReencodeComponent import aws.smithy.kotlin.runtime.time.TimestampFormat import kotlinx.coroutines.withContext @@ -83,20 +82,28 @@ internal class DefaultCanonicalizer(private val sha256Supplier: HashSupplier = : val signViaQueryParams = config.signatureType == AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS val addHashHeader = !signViaQueryParams && config.signedBodyHeader == AwsSignedBodyHeader.X_AMZ_CONTENT_SHA256 - val sessionToken = config.credentials.sessionToken?.let { if (signViaQueryParams) it.urlEncodeComponent() else it } + val sessionToken = config.credentials.sessionToken val builder = request.toBuilder() - val params = when (config.signatureType) { - AwsSignatureType.HTTP_REQUEST_VIA_HEADERS -> builder.headers - AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS -> builder.url.parameters - else -> TODO("Support for ${config.signatureType} is not yet implemented") - } - fun param(name: String, value: String?, predicate: Boolean = true) { - if (predicate && value != null) params[name] = value + fun param(name: String, value: String?, predicate: Boolean = true, overwrite: Boolean = true) { + if (predicate && value != null) { + when (config.signatureType) { + AwsSignatureType.HTTP_REQUEST_VIA_HEADERS -> { + if (overwrite || name !in builder.headers) builder.headers[name] = value + } + + AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS -> { + val params = builder.url.parameters.decodedParameters + if (overwrite || name !in params) params.put(name, value) + } + + else -> TODO("Support for ${config.signatureType} is not yet implemented") + } + } } - param("Host", builder.url.host.toString(), !(signViaQueryParams || "Host" in params)) + param("Host", builder.url.host.toString(), !signViaQueryParams, overwrite = false) param("X-Amz-Algorithm", ALGORITHM_NAME, signViaQueryParams) param("X-Amz-Credential", credentialValue(config), signViaQueryParams) param("X-Amz-Content-Sha256", hash, addHashHeader) @@ -181,32 +188,37 @@ internal class DefaultCanonicalizer(private val sha256Supplier: HashSupplier = : private const val STREAM_CHUNK_BYTES = 16384 // 16KB /** - * Canonicalizes a path from this [UrlBuilder]. + * Canonicalizes a path from this [Url.Builder]. * @param config The signing configuration to use * @return The canonicalized path */ -internal fun UrlBuilder.canonicalPath(config: AwsSigningConfig): String { - val segmentTransform = if (config.useDoubleUriEncode) { s: String -> s.encodeLabel() } else null - val pathTransform = if (config.normalizeUriPath) String::normalizePathSegments else String::transformPathSegments - return pathTransform(path.trim(), segmentTransform) +internal fun Url.Builder.canonicalPath(config: AwsSigningConfig): String { + val srcPath = path + val srcSegments = srcPath.encodedSegments + val destSegments = if (config.useDoubleUriEncode) srcSegments.map(PercentEncoding.Path::encode) else srcSegments + return UrlPath { + encodedSegments.addAll(destSegments) + trailingSlash = srcPath.trailingSlash + if (config.normalizeUriPath) normalize() + }.toString() } /** - * Canonicalizes the query parameters from this [UrlBuilder]. + * Canonicalizes the query parameters from this [Url.Builder]. * @return The canonicalized query parameters */ -private fun UrlBuilder.canonicalQueryParams(): String = parameters - .entries() - .map { it.key.urlReencodeComponent() to it.value } - .sortedBy { it.first } - .flatMap { it.asQueryParamComponents() } - .joinToString(separator = "&") - -private fun Pair>.asQueryParamComponents(): List = - second - .map { this@asQueryParamComponents.first to it.urlReencodeComponent() } - .sortedBy { it.second } - .map { "${it.first}=${it.second}" } +internal fun Url.Builder.canonicalQueryParams(): String { + val canonicalized = parameters + .entries + .associate { (key, values) -> key.reencode().encoded to values.map { it.reencode().encoded } } // FIXME 🤮 + .entries + .sortedWith(compareBy { it.key }) // Sort keys + .associate { (key, values) -> key to values.sorted().toMutableList() } // Sort values + + return QueryParameters { + encodedParameters.putAll(canonicalized) + }.toString().removePrefix("?") +} private fun Pair>.canonicalLine(): String { val valuesString = second.joinToString(separator = ",") { it.trimAll() } diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt index 0930d69a2..68585391c 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt @@ -5,7 +5,6 @@ package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.http.request.HttpRequest -import aws.smithy.kotlin.runtime.text.urlReencodeComponent /** * An object that can mutate requests to include signing attributes. @@ -22,7 +21,6 @@ internal interface RequestMutator { * Appends authorization information to a canonical request, returning a new request ready to be sent. * @param config The signing configuration to use * @param canonical The [CanonicalRequest] which has already been modified - * @param credentials The retrieved credentials used in the signing process * @param signatureHex The signature as a hex string * @return A new [HttpRequest] containing all the relevant signing/authorization attributes which is ready to be * sent. @@ -49,14 +47,14 @@ internal class DefaultRequestMutator : RequestMutator { } AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS -> { - with(canonical.request.url.parameters) { - set("X-Amz-Signature", signatureHex) + canonical.request.url.parameters.decodedParameters.put("X-Amz-Signature", signatureHex) - entries().forEach { - remove(it.key) - appendAll(it.key, it.value.map(String::urlReencodeComponent)) - } + /* TODO don't need to reencode because their already canonicalized by `Encodable`? + entries().forEach { + remove(it.key) + appendAll(it.key, it.value.map(String::urlReencodeComponent)) } + */ } else -> TODO("Support for ${config.signatureType} is not yet implemented") diff --git a/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultCanonicalizerTest.kt b/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultCanonicalizerTest.kt index 5b1eb042c..aa26db234 100644 --- a/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultCanonicalizerTest.kt +++ b/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultCanonicalizerTest.kt @@ -9,8 +9,7 @@ import aws.smithy.kotlin.runtime.auth.awssigning.tests.DEFAULT_TEST_CREDENTIALS import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.request.* import aws.smithy.kotlin.runtime.net.Host -import aws.smithy.kotlin.runtime.net.UrlBuilder -import aws.smithy.kotlin.runtime.net.parameters +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.time.Instant import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -24,10 +23,9 @@ class DefaultCanonicalizerTest { method = HttpMethod.GET url { host = Host.Domain("iam.amazonaws.com") - path = "" - parameters { - set("Action", "ListUsers") - set("Version", "2010-05-08") + parameters.decodedParameters { + add("Action", "ListUsers") + add("Version", "2010-05-08") } } headers { @@ -81,8 +79,8 @@ class DefaultCanonicalizerTest { // Targeted test for proper URI path escaping. See https://github.com/awslabs/smithy-kotlin/issues/657 @Test fun testEscapablePath() { - val uri = UrlBuilder() - uri.path = "/2013-04-01/healthcheck/foo%3Cbar%3Ebaz%3C%2Fbar%3E" + val uri = Url.Builder() + uri.path.encoded = "/2013-04-01/healthcheck/foo%3Cbar%3Ebaz%3C%2Fbar%3E" val config = AwsSigningConfig { normalizeUriPath = true @@ -95,6 +93,35 @@ class DefaultCanonicalizerTest { assertEquals("/2013-04-01/healthcheck/foo%253Cbar%253Ebaz%253C%252Fbar%253E", uri.canonicalPath(config)) } + @Test + fun testCanonicalPath() { + val config = AwsSigningConfig { + normalizeUriPath = true + useDoubleUriEncode = true + region = "the-moon" + service = "landing-pad" + credentials = DEFAULT_TEST_CREDENTIALS + } + + val uri = Url.Builder() + uri.path.encoded = "/foo/bar/baz%3Cqux%3Aquux" + assertEquals("/foo/bar/baz%253Cqux%253Aquux", uri.canonicalPath(config)) + } + + @Test + fun testCanonicalQueryParams() { + Url.Builder().apply { + parameters { + decodedParameters { + addAll("foo", listOf("banana", "apple", "cherry")) + addAll("bar", listOf("elderberry", "date")) + } + assertEquals("?foo=banana&foo=apple&foo=cherry&bar=elderberry&bar=date", decoded) + } + assertEquals("bar=date&bar=elderberry&foo=apple&foo=banana&foo=cherry", canonicalQueryParams()) + } + } + @Test fun testUnsignedHeaders() = runTest { val request = HttpRequest { diff --git a/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultRequestMutatorTest.kt b/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultRequestMutatorTest.kt index 3f768d3a5..7eb10ab69 100644 --- a/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultRequestMutatorTest.kt +++ b/runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultRequestMutatorTest.kt @@ -10,7 +10,6 @@ import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.HttpMethod import aws.smithy.kotlin.runtime.http.request.* import aws.smithy.kotlin.runtime.net.Host -import aws.smithy.kotlin.runtime.net.parameters import aws.smithy.kotlin.runtime.time.Instant import kotlin.test.Test import kotlin.test.assertEquals @@ -52,11 +51,11 @@ private val baseRequest = HttpRequest { method = HttpMethod.GET url { host = Host.Domain("foo.com") - path = "bar/baz" - parameters { - append("a", "apple") - append("b", "banana") - append("c", "cherry") + path.decoded = "bar/baz" + parameters.decodedParameters { + add("a", "apple") + add("b", "banana") + add("c", "cherry") } } headers { diff --git a/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/BasicSigningTestBase.kt b/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/BasicSigningTestBase.kt index 34050db13..2a517fc09 100644 --- a/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/BasicSigningTestBase.kt +++ b/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/BasicSigningTestBase.kt @@ -14,7 +14,7 @@ import aws.smithy.kotlin.runtime.http.request.headers import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.time.Instant import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.test.runTest @@ -62,7 +62,7 @@ public abstract class BasicSigningTestBase : HasSigner { method = HttpMethod.POST url.scheme = Scheme.HTTP url.host = Host.Domain("demo.us-east-1.amazonaws.com") - url.path = "/" + url.path.encoded = "/" headers.append("Host", "demo.us-east-1.amazonaws.com") headers.appendAll("x-amz-archive-description", listOf("test", "test")) val requestBody = "{\"TableName\": \"foo\"}" @@ -90,7 +90,7 @@ public abstract class BasicSigningTestBase : HasSigner { method = HttpMethod.POST url.scheme = Scheme.HTTP url.host = Host.Domain("demo.us-east-1.amazonaws.com") - url.path = "/" + url.path.encoded = "/" headers.append("Host", "demo.us-east-1.amazonaws.com") headers.appendAll("x-amz-archive-description", listOf("test", "test")) val requestBody = "{\"TableName\": \"foo\"}" @@ -195,7 +195,7 @@ public abstract class BasicSigningTestBase : HasSigner { method = HttpMethod.POST url.scheme = Scheme.HTTP url.host = Host.Domain("test.amazonaws.com") - url.path = "/" + url.path.encoded = "/" headers.append("Host", "test.amazonaws.com") headers.appendAll("x-amz-archive-description", listOf("test", "test")) body = HttpBody.fromBytes("body".encodeToByteArray()) diff --git a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt index 2799d4f25..ac9301cbc 100644 --- a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt +++ b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt @@ -18,7 +18,7 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.httptest.TestEngine import aws.smithy.kotlin.runtime.identity.asIdentityProviderConfig -import aws.smithy.kotlin.runtime.net.fullUriToQueryParameters +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.util.Attributes @@ -146,8 +146,8 @@ public actual abstract class SigningSuiteTestBase : HasSigner { testSigv4Middleware(test) } - private fun getTests(signatureType: AwsSignatureType): List { - val tests = testDirPaths.map { dir -> + private fun getTests(signatureType: AwsSignatureType): List = + testDirPaths.map { dir -> try { val req = getRequest(dir) val config = getSigningConfig(dir) ?: defaultTestSigningConfig @@ -162,8 +162,6 @@ public actual abstract class SigningSuiteTestBase : HasSigner { throw ex } } - return tests - } /** * Run a test from the suite against the AwsSigv4Middleware implementation @@ -273,15 +271,7 @@ public actual abstract class SigningSuiteTestBase : HasSigner { val extraHeaders = actual.headers.lowerKeys() - expected.headers.lowerKeys() assertEquals(0, extraHeaders.size, "Found extra headers in request: $extraHeaders") - expected.url.parameters.forEach { key, values -> - val expectedValues = values.sorted().joinToString(separator = ", ") - val actualValues = actual.url.parameters.getAll(key)?.sorted()?.joinToString(separator = ", ") - assertNotNull(actualValues, "expected query key `$key` not found in actual signed request") - assertEquals(expectedValues, actualValues, "expected query param `$key=$expectedValues` in signed request") - } - - val extraParams = actual.url.parameters.lowerKeys() - expected.url.parameters.lowerKeys() - assertEquals(0, extraParams.size, "Found extra query params in request: $extraParams") + assertEquals(expected.url.parameters, actual.url.parameters) when (val expectedBody = expected.body) { is HttpBody.Empty -> assertIs(actual.body) @@ -362,7 +352,6 @@ public actual abstract class SigningSuiteTestBase : HasSigner { /** * Parse a path containing an HTTP request into an in memory representation of an SDK request */ - @OptIn(InternalAPI::class) private fun parseRequest(path: Path): HttpRequestBuilder { // we have to do some massaging of these input files to get a valid request out of the parser. var text = path.readText() @@ -382,10 +371,7 @@ public actual abstract class SigningSuiteTestBase : HasSigner { val builder = HttpRequestBuilder() builder.method = HttpMethod.parse(parsed.method.value) - builder.url.path = parsed.parsePath() - parsed.uri.fullUriToQueryParameters()?.let { - builder.url.parameters.appendAll(it) - } + builder.url.copyFrom(parsed) val parsedHeaders = CIOHeaders(parsed.headers) parsedHeaders.forEach { key, values -> @@ -401,20 +387,18 @@ public actual abstract class SigningSuiteTestBase : HasSigner { } } +private fun Url.Builder.copyFrom(cioRequest: Request) { + val parts = cioRequest.uri.split('?', limit = 2) + path.encoded = parts[0] + parts.getOrNull(1)?.let { parameters.encoded = it } +} + private fun jsonCredentials(jsonObject: JsonObject): Credentials = Credentials( jsonObject["access_key_id"]!!.jsonPrimitive.content, jsonObject["secret_access_key"]!!.jsonPrimitive.content, jsonObject["token"]?.jsonPrimitive?.content, ) -/** - * parse path from ktor request uri - */ -private fun Request.parsePath(): String { - val idx = uri.indexOf("?") - return if (idx > 0) uri.substring(0, idx) else uri.toString() -} - /** * Construct on SdkHttpOperation for testing with middleware * diff --git a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt index a2c4b9339..7a207cc0b 100644 --- a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt +++ b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt @@ -189,11 +189,11 @@ private fun HttpRequestBuilder.update(signedRequest: HttpRequest) { this.headers.appendMissing(key, values) } - signedRequest.url.parameters.forEach { key, values -> + signedRequest.url.parameters.forEach { (key, values) -> // The signed request has a URL-encoded path which means simply appending missing could result in both the raw // and percent-encoded value being present. Instead, just append new keys added by signing. - if (!this.url.parameters.contains(key)) { - url.parameters.appendAll(key, values) + if (key !in url.parameters) { + url.parameters.addAll(key, values) } } } diff --git a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt index 09474c8bd..8cfcd1f86 100644 --- a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt +++ b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt @@ -54,7 +54,7 @@ public abstract class AwsHttpSignerTestBase( method = HttpMethod.POST url.scheme = Scheme.HTTP url.host = Host.Domain("demo.us-east-1.amazonaws.com") - url.path = "/" + url.path.encoded = "/" headers.append("Host", "demo.us-east-1.amazonaws.com") headers.appendAll("x-amz-archive-description", listOf("test", "test")) body = when (streaming) { diff --git a/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt b/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt index 6f10e029c..6c627d12b 100644 --- a/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt +++ b/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt @@ -10,8 +10,10 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder -import aws.smithy.kotlin.runtime.net.QueryParameters -import aws.smithy.kotlin.runtime.net.splitAsQueryParameters +import aws.smithy.kotlin.runtime.net.url.QueryParameters +import aws.smithy.kotlin.runtime.net.url.Url +import aws.smithy.kotlin.runtime.text.ensurePrefix +import aws.smithy.kotlin.runtime.text.ensureSuffix import kotlin.coroutines.coroutineContext import aws.sdk.kotlin.crt.http.Headers as HeadersCrt import aws.sdk.kotlin.crt.http.HttpRequest as HttpRequestCrt @@ -41,7 +43,7 @@ public suspend fun HttpRequest.toSignableCrtRequest( ): HttpRequestCrt = HttpRequestCrt( method = method.name, - encodedPath = url.encodedPath, + encodedPath = url.requestRelativePath, headers = headers.toCrtHeaders(), body = signableBodyStream(body, unsignedPayload, awsChunked), ) @@ -66,12 +68,12 @@ public fun HttpRequestBuilder.update(crtRequest: HttpRequestCrt) { if (crtRequest.encodedPath.isNotBlank()) { crtRequest.queryParameters()?.let { - it.forEach { key, values -> + it.forEach { (key, values) -> // the crt request has a url encoded path which means // simply appending missing could result in both the raw and percent-encoded // value being present. Instead just append new keys added by signing - if (!url.parameters.contains(key)) { - url.parameters.appendAll(key, values) + if (key !in url.parameters) { + url.parameters.addAll(key, values) } } } @@ -88,8 +90,8 @@ public fun HttpRequestCrt.queryParameters(): QueryParameters? { if (idx < 0 || idx + 1 > encodedPath.length) return null val fragmentIdx = encodedPath.indexOf("#", startIndex = idx) - val rawQueryString = if (fragmentIdx > 0) encodedPath.substring(idx + 1, fragmentIdx) else encodedPath.substring(idx + 1) - return rawQueryString.splitAsQueryParameters() + val rawQueryString = if (fragmentIdx > 0) encodedPath.substring(idx, fragmentIdx) else encodedPath.substring(idx) + return QueryParameters.parseEncoded(rawQueryString) } /** diff --git a/runtime/crt-util/jvm/test/aws/smithy/kotlin/runtime/crt/HttpTest.kt b/runtime/crt-util/jvm/test/aws/smithy/kotlin/runtime/crt/HttpTest.kt index ce482d5e6..d203a80e4 100644 --- a/runtime/crt-util/jvm/test/aws/smithy/kotlin/runtime/crt/HttpTest.kt +++ b/runtime/crt-util/jvm/test/aws/smithy/kotlin/runtime/crt/HttpTest.kt @@ -11,8 +11,6 @@ import aws.smithy.kotlin.runtime.http.request.headers import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.net.encodedPath -import aws.smithy.kotlin.runtime.net.parameters import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -30,10 +28,8 @@ class HttpTest { scheme = Scheme.HTTPS host = Host.Domain("test.com") port = 3000 - path = "/foo/bar/baz" - parameters { - append("foo", "bar") - } + path.encoded = "/foo/bar/baz" + parameters.decodedParameters.add("foo", "bar") } headers { @@ -66,10 +62,10 @@ class HttpTest { } } - assertEquals("/foo/bar/baz", builder.url.path) + assertEquals("/foo/bar/baz", builder.url.path.encoded) - assertTrue(builder.url.parameters.contains("foo", "bar")) - assertTrue(builder.url.parameters.contains("baz", "quux")) + assertTrue(builder.url.parameters.decodedParameters.contains("foo", "bar")) + assertTrue(builder.url.parameters.decodedParameters.contains("baz", "quux")) } @Test @@ -79,7 +75,7 @@ class HttpTest { url { scheme = Scheme.HTTPS host = Host.Domain("test.com") - path = "/foo" + path.encoded = "/foo" } } @@ -95,7 +91,7 @@ class HttpTest { assertEquals(Host.Domain("test.com"), builder.url.host) assertEquals(Scheme.HTTPS, builder.url.scheme) - assertEquals("/foo", builder.url.path) + assertEquals("/foo", builder.url.path.encoded) } @Test @@ -108,22 +104,20 @@ class HttpTest { scheme = Scheme.HTTPS host = Host.Domain("test.com") port = 3000 - path = "/foo/bar/baz" - parameters { - append("foo", "/") - } + path.encoded = "/foo/bar/baz" + parameters.decodedParameters.add("foo", "/") } } // build a slightly modified crt request (e.g. after signing new headers or query params will be present) val crtHeaders = HeadersCrt.build { } - val crtRequest = HttpRequestCrt("POST", builder.url.encodedPath, crtHeaders, null) + val crtRequest = HttpRequestCrt("POST", builder.url.path.encoded, crtHeaders, null) builder.update(crtRequest) - assertEquals("/foo/bar/baz", builder.url.path) + assertEquals("/foo/bar/baz", builder.url.path.encoded) - val values = builder.url.parameters.getAll("foo")!! + val values = builder.url.parameters.decodedParameters.getValue("foo") assertEquals(1, values.size) assertEquals("/", values.first()) } diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt index 2da86f823..2b12dbf4f 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt @@ -92,9 +92,12 @@ internal class ConnectionManager( is ProxyConfig.Http -> HttpProxyOptions( proxyConfig.url.host.toString(), proxyConfig.url.port, - authUsername = proxyConfig.url.userInfo?.username, - authPassword = proxyConfig.url.userInfo?.password, - authType = if (proxyConfig.url.userInfo != null) HttpProxyAuthorizationType.Basic else HttpProxyAuthorizationType.None, + authUsername = proxyConfig.url.userInfo.userName.decoded, + authPassword = proxyConfig.url.userInfo.password.decoded, + authType = when { + proxyConfig.url.userInfo.isNotEmpty -> HttpProxyAuthorizationType.Basic + else -> HttpProxyAuthorizationType.None + } ) else -> null } diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/RequestUtil.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/RequestUtil.kt index e969d0856..a12d11051 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/RequestUtil.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/RequestUtil.kt @@ -34,7 +34,10 @@ internal val HttpRequest.uri: Uri scheme = Protocol.createOrDefault(sdkUrl.scheme.protocolName) host = sdkUrl.host.toString() port = sdkUrl.port - userInfo = sdkUrl.userInfo?.let { UserInfo(it.username, it.password) } + userInfo = sdkUrl + .userInfo + .takeIf { it.isNotEmpty } + ?.let { UserInfo(it.userName.decoded, it.password.decoded) } // the rest is part of each individual request, manager only needs the host info } } @@ -67,7 +70,7 @@ internal fun HttpRequest.toCrtRequest(callContext: CoroutineContext): aws.sdk.ko val contentLength = body.contentLength?.takeIf { it >= 0 }?.toString() ?: headers[CONTENT_LENGTH_HEADER] contentLength?.let { crtHeaders.append(CONTENT_LENGTH_HEADER, it) } - return aws.sdk.kotlin.crt.http.HttpRequest(method.name, url.encodedPath, crtHeaders.build(), bodyStream) + return aws.sdk.kotlin.crt.http.HttpRequest(method.name, url.requestRelativePath, crtHeaders.build(), bodyStream) } /** diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/AsyncStressTest.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/AsyncStressTest.kt index a7963fd2d..7f04debea 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/AsyncStressTest.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/AsyncStressTest.kt @@ -53,7 +53,7 @@ class AsyncStressTest : TestWithLocalServer() { method = HttpMethod.GET host = Host.Domain(testHost) port = serverPort - path = "/largeResponse" + path.decoded = "/largeResponse" } } diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/RequestConversionTest.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/RequestConversionTest.kt index 2c0e9f331..88e88561e 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/RequestConversionTest.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/test/aws/smithy/kotlin/runtime/http/engine/crt/RequestConversionTest.kt @@ -12,7 +12,7 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.io.SdkByteReadChannel import aws.smithy.kotlin.runtime.io.SdkSource import aws.smithy.kotlin.runtime.io.source -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlin.coroutines.EmptyCoroutineContext diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt index 5b793a33c..15cb9bad3 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt @@ -14,6 +14,8 @@ import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.io.SdkSource import aws.smithy.kotlin.runtime.io.internal.toSdk import aws.smithy.kotlin.runtime.net.* +import aws.smithy.kotlin.runtime.net.url.Url +import aws.smithy.kotlin.runtime.net.url.UserInfo import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant import kotlinx.coroutines.* @@ -113,7 +115,11 @@ internal class OkHttpProxyAuthenticator( } val url = response.request.url.let { - Url(scheme = Scheme(it.scheme, it.port), host = Host.parse(it.host), port = it.port) + Url { + scheme = Scheme(it.scheme, it.port) + host = Host.parse(it.host) + port = it.port + } } // NOTE: We will end up querying the proxy selector twice. We do this to allow @@ -128,7 +134,7 @@ internal class OkHttpProxyAuthenticator( for (challenge in response.challenges()) { if (challenge.scheme.lowercase() == "okhttp-preemptive" || challenge.scheme == "Basic") { return response.request.newBuilder() - .header("Proxy-Authorization", Credentials.basic(userInfo.username, userInfo.password)) + .header("Proxy-Authorization", userInfo.toBasicCredentials()) .build() } } @@ -137,6 +143,8 @@ internal class OkHttpProxyAuthenticator( } } +private fun UserInfo.toBasicCredentials() = Credentials.basic(userName.decoded, password.decoded) + internal class OkHttpDns( private val hr: HostResolver, ) : Dns { @@ -183,24 +191,24 @@ internal class OkHttpCall( private fun URI.toUrl(): Url { val uri = this - return UrlBuilder { + return Url { scheme = Scheme.parse(uri.scheme) // OkHttp documentation calls out that v6 addresses will contain the []s host = Host.parse(if (uri.host.startsWith("[")) uri.host.substring(1 until uri.host.length - 1) else uri.host) port = uri.port.takeIf { it > 0 } - path = uri.path + path.encoded = uri.rawPath - if (uri.query != null && uri.query.isNotBlank()) { - val parsedParameters = uri.query.splitAsQueryParameters() - parameters.appendAll(parsedParameters) + if (!uri.rawQuery.isNullOrBlank()) { + parameters.encoded = uri.rawQuery } - userInfo = uri.userInfo?.takeIf { it.isNotBlank() } - ?.let(::UserInfo) + if (!uri.rawUserInfo.isNullOrBlank()) { + userInfo.copyFrom(UserInfo.parseEncoded(uri.rawUserInfo)) + } - fragment = uri.fragment?.takeIf { it.isNotBlank() } + encodedFragment = uri.rawFragment } } diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpRequestTest.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpRequestTest.kt index ff03fdcf4..cf03d6d90 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpRequestTest.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpRequestTest.kt @@ -10,6 +10,7 @@ import aws.smithy.kotlin.runtime.http.engine.internal.HttpClientMetrics import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.io.SdkByteReadChannel import aws.smithy.kotlin.runtime.net.* +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider import okio.Buffer @@ -25,16 +26,16 @@ class OkHttpRequestTest { @Test fun itConvertsUrls() { - val url = UrlBuilder().apply { + val url = Url { scheme = Scheme.HTTPS host = Host.Domain("aws.amazon.com") - path = "/foo%2Fbar/qux" - parameters { - append("q", "dogs") - append("q", "&") - append("q", "lep ball") + path.encoded = "/foo%2Fbar/qux" + parameters.decodedParameters { + add("q", "dogs") + add("q", "&") + add("q", "lep ball") } - }.build() + } // check our encoding val expectedUrl = "https://aws.amazon.com/foo%2Fbar/qux?q=dogs&q=%26&q=lep%20ball" diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpResponseTest.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpResponseTest.kt index 77ceca5be..1067f0963 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpResponseTest.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpResponseTest.kt @@ -8,7 +8,7 @@ package aws.smithy.kotlin.runtime.http.engine.okhttp import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.engine.internal.HttpClientMetrics import aws.smithy.kotlin.runtime.http.request.HttpRequest -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider import kotlinx.coroutines.* diff --git a/runtime/protocol/http-client-engines/test-suite/common/src/aws/smithy/kotlin/runtime/http/test/util/AbstractEngineTest.kt b/runtime/protocol/http-client-engines/test-suite/common/src/aws/smithy/kotlin/runtime/http/test/util/AbstractEngineTest.kt index 593c31b1e..664616a3c 100644 --- a/runtime/protocol/http-client-engines/test-suite/common/src/aws/smithy/kotlin/runtime/http/test/util/AbstractEngineTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/common/src/aws/smithy/kotlin/runtime/http/test/util/AbstractEngineTest.kt @@ -11,7 +11,7 @@ import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfig import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.url -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext diff --git a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/AsyncStressTest.kt b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/AsyncStressTest.kt index 4ea0a6605..4f06faa0f 100644 --- a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/AsyncStressTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/AsyncStressTest.kt @@ -31,7 +31,7 @@ class AsyncStressTest : AbstractEngineTest() { test { env, client -> val req = HttpRequest { testSetup(env) - url.path = "concurrent" + url.path.decoded = "concurrent" } val call = client.call(req) @@ -57,7 +57,7 @@ class AsyncStressTest : AbstractEngineTest() { test { env, client -> val req = HttpRequest { testSetup(env) - url.path = "concurrent" + url.path.decoded = "concurrent" } val engineJobsBefore = client.engine.coroutineContext.job.children.toList() diff --git a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt index 3027549ff..bbac61081 100644 --- a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/DownloadTest.kt @@ -66,7 +66,7 @@ class DownloadTest : AbstractEngineTest() { test { env, client -> val req = HttpRequest { testSetup(env) - url.path = "/download/integrity" + url.path.decoded = "/download/integrity" } val call = client.call(req) @@ -93,8 +93,8 @@ class DownloadTest : AbstractEngineTest() { test { env, client -> val req = HttpRequest { testSetup(env) - url.path = "/download/integrity" - url.parameters.append("chunked-response", "true") + url.path.decoded = "/download/integrity" + url.parameters.decodedParameters.add("chunked-response", "true") } val call = client.call(req) @@ -124,7 +124,7 @@ class DownloadTest : AbstractEngineTest() { test { env, client -> val req = HttpRequest { testSetup(env) - url.path = "/download/empty" + url.path.decoded = "/download/empty" } val call = client.call(req) diff --git a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/RedirectTest.kt b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/RedirectTest.kt index 522e642f2..4e75c035d 100644 --- a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/RedirectTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/RedirectTest.kt @@ -26,7 +26,7 @@ class RedirectTest : AbstractEngineTest() { test { env, client -> val req = HttpRequest { testSetup(env) - url.path = path + url.path.decoded = path } val call = client.call(req) diff --git a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt index 6dab2622e..51642fbb9 100644 --- a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/UploadTest.kt @@ -33,7 +33,7 @@ class UploadTest : AbstractEngineTest() { val req = HttpRequest { method = HttpMethod.POST testSetup(env) - url.path = "/upload/content" + url.path.decoded = "/upload/content" body = block() } @@ -80,7 +80,7 @@ class UploadTest : AbstractEngineTest() { val req = HttpRequest { method = HttpMethod.POST testSetup(env) - url.path = "/upload/content" + url.path.decoded = "/upload/content" body = content } @@ -115,7 +115,7 @@ class UploadTest : AbstractEngineTest() { val req = HttpRequest { method = HttpMethod.POST testSetup(env) - url.path = "/upload/content" + url.path.decoded = "/upload/content" body = content } @@ -153,7 +153,7 @@ class UploadTest : AbstractEngineTest() { val req = HttpRequest { method = HttpMethod.POST testSetup(env) - url.path = "/upload/content" + url.path.decoded = "/upload/content" body = wrappedStream.toHttpBody() } @@ -175,7 +175,7 @@ class UploadTest : AbstractEngineTest() { append("Content-Type", "application/xml") } testSetup(env) - url.path = "/upload/content" + url.path.decoded = "/upload/content" body = HttpBody.Empty } @@ -212,7 +212,7 @@ class UploadTest : AbstractEngineTest() { val req = HttpRequest { method = HttpMethod.POST testSetup(env) - url.path = "/upload/content" + url.path.decoded = "/upload/content" body = chan.toHttpBody(16 * 1024 * 1024) } diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt index 1e27b4666..b876a7d72 100644 --- a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/FileUploadDownloadTest.kt @@ -33,7 +33,7 @@ class FileUploadDownloadTest : AbstractEngineTest() { val req = HttpRequest { method = HttpMethod.POST testSetup(env) - url.path = "/upload/content" + url.path.decoded = "/upload/content" body = httpBody } @@ -49,7 +49,7 @@ class FileUploadDownloadTest : AbstractEngineTest() { test { env, client -> val req = HttpRequest { testSetup(env) - url.path = "/download/integrity" + url.path.decoded = "/download/integrity" } val call = client.call(req) diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ProxyTest.kt b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ProxyTest.kt index 3a36e7d60..3ed4a6575 100644 --- a/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ProxyTest.kt +++ b/runtime/protocol/http-client-engines/test-suite/jvm/test/aws/smithy/kotlin/runtime/http/test/ProxyTest.kt @@ -17,7 +17,7 @@ import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.http.test.util.AbstractEngineTest import aws.smithy.kotlin.runtime.http.test.util.engineConfig import aws.smithy.kotlin.runtime.http.test.util.test -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledIfSystemProperty import org.testcontainers.containers.BindMode @@ -102,8 +102,7 @@ private suspend fun testProxyResponse(client: SdkHttpClient) { // NOTE: you can still use mitmproxy to proxy https traffic without intercepting it // like any normal proxy by setting the `--ignore-hosts` option to forward all traffic // but that would mean we make an actual request to the origin - url(Url.parse("http://aws.amazon.com")) - url.path = "/" + url(Url.parse("http://aws.amazon.com/")) header("Host", "aws.amazon.com") } diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 2641ece0b..31ddd9a8d 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -124,20 +124,20 @@ public final class aws/smithy/kotlin/runtime/http/engine/ProxyConfig$Direct : aw } public final class aws/smithy/kotlin/runtime/http/engine/ProxyConfig$Http : aws/smithy/kotlin/runtime/http/engine/ProxyConfig { - public fun (Laws/smithy/kotlin/runtime/net/Url;)V + public fun (Laws/smithy/kotlin/runtime/net/url/Url;)V public fun (Ljava/lang/String;)V - public final fun component1 ()Laws/smithy/kotlin/runtime/net/Url; - public final fun copy (Laws/smithy/kotlin/runtime/net/Url;)Laws/smithy/kotlin/runtime/http/engine/ProxyConfig$Http; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/http/engine/ProxyConfig$Http;Laws/smithy/kotlin/runtime/net/Url;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/http/engine/ProxyConfig$Http; + public final fun component1 ()Laws/smithy/kotlin/runtime/net/url/Url; + public final fun copy (Laws/smithy/kotlin/runtime/net/url/Url;)Laws/smithy/kotlin/runtime/http/engine/ProxyConfig$Http; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/http/engine/ProxyConfig$Http;Laws/smithy/kotlin/runtime/net/url/Url;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/http/engine/ProxyConfig$Http; public fun equals (Ljava/lang/Object;)Z - public final fun getUrl ()Laws/smithy/kotlin/runtime/net/Url; + public final fun getUrl ()Laws/smithy/kotlin/runtime/net/url/Url; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class aws/smithy/kotlin/runtime/http/engine/ProxySelector { public static final field Companion Laws/smithy/kotlin/runtime/http/engine/ProxySelector$Companion; - public abstract fun select (Laws/smithy/kotlin/runtime/net/Url;)Laws/smithy/kotlin/runtime/http/engine/ProxyConfig; + public abstract fun select (Laws/smithy/kotlin/runtime/net/url/Url;)Laws/smithy/kotlin/runtime/http/engine/ProxyConfig; } public final class aws/smithy/kotlin/runtime/http/engine/ProxySelector$Companion { diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelector.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelector.kt index c92a409ec..d656ff759 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelector.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelector.kt @@ -8,8 +8,11 @@ package aws.smithy.kotlin.runtime.http.engine import aws.smithy.kotlin.runtime.ClientException import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.net.Url -import aws.smithy.kotlin.runtime.util.* +import aws.smithy.kotlin.runtime.net.url.Url +import aws.smithy.kotlin.runtime.util.EnvironmentProvider +import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider +import aws.smithy.kotlin.runtime.util.PlatformProvider +import aws.smithy.kotlin.runtime.util.PropertyProvider /** * Select a proxy via environment. This selector will look for @@ -61,7 +64,11 @@ private fun resolveProxyByProperty(provider: PropertyProvider, scheme: Scheme): val proxyProtocol = Scheme.HTTP val url = try { - Url(proxyProtocol, Host.parse(hostName), proxyPortProp?.toInt() ?: scheme.defaultPort) + Url { + this.scheme = proxyProtocol + host = Host.parse(hostName) + proxyPortProp?.let { port = it.toInt() } + } } catch (e: Exception) { val parsed = buildString { append("""$hostPropName="$proxyHostProp"""") diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxyConfig.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxyConfig.kt index 959ad878e..9bcbf8280 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxyConfig.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxyConfig.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.http.engine -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url /** * A proxy configuration diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxySelector.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxySelector.kt index 8c78d59de..72ecb83c6 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxySelector.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/ProxySelector.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.http.engine -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url /** * Selects the proxy to use for a given [Url]. Implementations **MUST** be stable and return the diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationEndpoint.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationEndpoint.kt index 6fe851b74..1ac27510d 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationEndpoint.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationEndpoint.kt @@ -8,6 +8,7 @@ package aws.smithy.kotlin.runtime.http.operation import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.client.endpoints.Endpoint import aws.smithy.kotlin.runtime.http.request.HttpRequest +import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.identity.Identity import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.operation.ExecutionContext @@ -49,22 +50,21 @@ public data class ResolveEndpointRequest( public fun setResolvedEndpoint(req: SdkHttpRequest, endpoint: Endpoint) { val hostPrefix = req.context.getOrNull(HttpOperationContext.HostPrefix) ?: "" val hostname = "$hostPrefix${endpoint.uri.host}" - val joinedPath = buildString { - append(endpoint.uri.path.removeSuffix("/")) - if (req.subject.url.path.isNotBlank()) { - append("/") - append(req.subject.url.path.removePrefix("/")) + + req.subject.url { + // Can't use Url.Builder.copyFrom because we need to keep existing path/parameters and merge in new ones + scheme = endpoint.uri.scheme + userInfo.copyFrom(endpoint.uri.userInfo) + host = Host.parse(hostname) + port = endpoint.uri.port + path { + trailingSlash = trailingSlash || segments.isEmpty() && endpoint.uri.path.trailingSlash + segments.addAll(0, endpoint.uri.path.segments) } + parameters.addAll(endpoint.uri.parameters) + encodedFragment = endpoint.uri.fragment?.encoded } - req.subject.url.scheme = endpoint.uri.scheme - req.subject.url.userInfo = endpoint.uri.userInfo - req.subject.url.host = Host.parse(hostname) - req.subject.url.port = endpoint.uri.port - req.subject.url.path = joinedPath - req.subject.url.parameters.appendAll(endpoint.uri.parameters) - req.subject.url.fragment = endpoint.uri.fragment - req.subject.headers["Host"] = hostname endpoint.headers?.let { req.subject.headers.appendAll(it) } } diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelectorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelectorTest.kt index 7b24e7694..3ff4d0829 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelectorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/EnvironmentProxySelectorTest.kt @@ -6,7 +6,7 @@ package aws.smithy.kotlin.runtime.http.engine import aws.smithy.kotlin.runtime.ClientException -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider import kotlin.test.Test import kotlin.test.assertContains diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/NoProxyHostTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/NoProxyHostTest.kt index 5e0fb8b93..d00413044 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/NoProxyHostTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/engine/NoProxyHostTest.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.http.engine -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ContinueInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ContinueInterceptorTest.kt index a9f4dadd5..575e9e032 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ContinueInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/ContinueInterceptorTest.kt @@ -11,7 +11,7 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.header import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.io.SdkSource -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext import kotlinx.coroutines.test.runTest import kotlin.test.Test diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/OperationEndpointTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/OperationEndpointTest.kt index 56a29aef8..79104a292 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/OperationEndpointTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/OperationEndpointTest.kt @@ -9,7 +9,7 @@ import aws.smithy.kotlin.runtime.client.endpoints.Endpoint import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -43,14 +43,14 @@ class OperationEndpointTest { @Test fun testHostWithBasePath() = runTest { val endpoint = Endpoint(uri = Url.parse("https://api.test.com:8080/foo/bar")) - val request = SdkHttpRequest(HttpRequestBuilder().apply { url.path = "/operation" }) + val request = SdkHttpRequest(HttpRequestBuilder().apply { url.path.decoded = "/operation" }) setResolvedEndpoint(request, endpoint) val actual = request.subject.build() assertEquals(Host.Domain("api.test.com"), actual.url.host) assertEquals(Scheme.HTTPS, actual.url.scheme) assertEquals(8080, actual.url.port) - assertEquals("/foo/bar/operation", actual.url.path) + assertEquals("/foo/bar/operation", actual.url.path.toString()) } @Test @@ -60,50 +60,50 @@ class OperationEndpointTest { set(HttpOperationContext.HostPrefix, "prefix.") } - val request = SdkHttpRequest(context, HttpRequestBuilder().apply { url.path = "/operation" }) + val request = SdkHttpRequest(context, HttpRequestBuilder().apply { url.path.decoded = "/operation" }) setResolvedEndpoint(request, endpoint) val actual = request.subject.build() assertEquals(Host.Domain("prefix.api.test.com"), actual.url.host) assertEquals(Scheme.HTTP, actual.url.scheme) - assertEquals("/operation", actual.url.path) + assertEquals("/operation", actual.url.path.toString()) } @Test fun testEndpointPathPrefixWithNonEmptyPath() = runTest { val endpoint = Endpoint(uri = Url.parse("http://api.test.com/path/prefix/")) - val request = SdkHttpRequest(HttpRequestBuilder().apply { url.path = "/operation" }) + val request = SdkHttpRequest(HttpRequestBuilder().apply { url.path.decoded = "/operation" }) setResolvedEndpoint(request, endpoint) val actual = request.subject.build() assertEquals(Host.Domain("api.test.com"), actual.url.host) assertEquals(Scheme.HTTP, actual.url.scheme) - assertEquals("/path/prefix/operation", actual.url.path) + assertEquals("/path/prefix/operation", actual.url.path.toString()) } @Test fun testEndpointPathPrefixWithEmptyPath() = runTest { val endpoint = Endpoint(uri = Url.parse("http://api.test.com/path/prefix")) - val request = SdkHttpRequest(HttpRequestBuilder().apply { url.path = "" }) + val request = SdkHttpRequest(HttpRequestBuilder()) setResolvedEndpoint(request, endpoint) val actual = request.subject.build() assertEquals(Host.Domain("api.test.com"), actual.url.host) assertEquals(Scheme.HTTP, actual.url.scheme) - assertEquals("/path/prefix", actual.url.path) + assertEquals("/path/prefix", actual.url.path.toString()) } @Test fun testQueryParameters() = runTest { val endpoint = Endpoint(uri = Url.parse("http://api.test.com?foo=bar&baz=qux")) - val request = SdkHttpRequest(HttpRequestBuilder().apply { url.path = "/operation" }) + val request = SdkHttpRequest(HttpRequestBuilder().apply { url.path.decoded = "/operation" }) setResolvedEndpoint(request, endpoint) val actual = request.subject.build() assertEquals(Host.Domain("api.test.com"), actual.url.host) assertEquals(Scheme.HTTP, actual.url.scheme) - assertEquals("/operation", actual.url.path) - assertEquals("bar", actual.url.parameters["foo"]) - assertEquals("qux", actual.url.parameters["baz"]) + assertEquals("/operation", actual.url.path.toString()) + assertEquals("bar", actual.url.parameters.decodedParameters["foo"]!!.single()) + assertEquals("qux", actual.url.parameters.decodedParameters["baz"]!!.single()) } } diff --git a/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt b/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt index 094231402..da9f32081 100644 --- a/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt +++ b/runtime/protocol/http-test/common/src/aws/smithy/kotlin/runtime/httptest/HttpTrafficParser.kt @@ -10,7 +10,7 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.http.response.HttpResponse -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.text.encoding.decodeBase64Bytes import kotlinx.serialization.json.* diff --git a/runtime/protocol/http-test/common/test/aws/smithy/kotlin/runtime/httptest/TestConnectionTest.kt b/runtime/protocol/http-test/common/test/aws/smithy/kotlin/runtime/httptest/TestConnectionTest.kt index 8901277a1..b8f0a41f1 100644 --- a/runtime/protocol/http-test/common/test/aws/smithy/kotlin/runtime/httptest/TestConnectionTest.kt +++ b/runtime/protocol/http-test/common/test/aws/smithy/kotlin/runtime/httptest/TestConnectionTest.kt @@ -21,7 +21,7 @@ class TestConnectionTest { expect { request { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") body = HttpBody.fromBytes("tests for your tests".encodeToByteArray()) } @@ -32,7 +32,7 @@ class TestConnectionTest { val req = HttpRequestBuilder().apply { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") headers.append("x-qux", "quux") body = HttpBody.fromBytes("tests for your tests".encodeToByteArray()) @@ -48,7 +48,7 @@ class TestConnectionTest { expect { request { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") body = HttpBody.fromBytes("tests for your tests".encodeToByteArray()) } @@ -59,7 +59,7 @@ class TestConnectionTest { val req = HttpRequestBuilder().apply { url.host = Host.Domain("test.com") - url.path = "/tests-for-your-tests" + url.path.decoded = "/tests-for-your-tests" headers.append("x-foo", "bar") } client.call(req).complete() @@ -75,7 +75,7 @@ class TestConnectionTest { expect { request { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") headers.append("x-baz", "qux") } @@ -86,7 +86,7 @@ class TestConnectionTest { val req = HttpRequestBuilder().apply { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") } client.call(req).complete() @@ -102,7 +102,7 @@ class TestConnectionTest { expect { request { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") body = HttpBody.fromBytes("tests for your tests".encodeToByteArray()) } @@ -113,7 +113,7 @@ class TestConnectionTest { val req = HttpRequestBuilder().apply { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") body = HttpBody.fromBytes("tests are good".encodeToByteArray()) } @@ -130,7 +130,7 @@ class TestConnectionTest { expect { request { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") body = HttpBody.fromBytes("tests for your tests".encodeToByteArray()) } @@ -143,7 +143,7 @@ class TestConnectionTest { val req = HttpRequestBuilder().apply { url.host = Host.Domain("test.com") - url.path = "/turtles-all-the-way-down" + url.path.decoded = "/turtles-all-the-way-down" headers.append("x-foo", "bar") headers.append("x-qux", "quux") body = HttpBody.fromBytes("tests for your tests".encodeToByteArray()) @@ -200,8 +200,8 @@ class TestConnectionTest { val req = HttpRequestBuilder().apply { method = HttpMethod.POST url.host = Host.Domain("test.aws.com") - url.path = "/turtles-all-the-way-down" - url.parameters.append("q1", "v1") + url.path.decoded = "/turtles-all-the-way-down" + url.parameters.decodedParameters.add("q1", "v1") headers.append("foo", "bar") headers.appendAll("baz", listOf("one", "two")) body = HttpBody.fromBytes("tests for your tests".encodeToByteArray()) diff --git a/runtime/protocol/http/api/http.api b/runtime/protocol/http/api/http.api index 23f525eb6..8cc273d2e 100644 --- a/runtime/protocol/http/api/http.api +++ b/runtime/protocol/http/api/http.api @@ -255,7 +255,7 @@ public abstract interface class aws/smithy/kotlin/runtime/http/request/HttpReque public abstract fun getHeaders ()Laws/smithy/kotlin/runtime/http/Headers; public abstract fun getMethod ()Laws/smithy/kotlin/runtime/http/HttpMethod; public abstract fun getTrailingHeaders ()Laws/smithy/kotlin/runtime/http/DeferredHeaders; - public abstract fun getUrl ()Laws/smithy/kotlin/runtime/net/Url; + public abstract fun getUrl ()Laws/smithy/kotlin/runtime/net/url/Url; } public final class aws/smithy/kotlin/runtime/http/request/HttpRequest$Companion { @@ -271,7 +271,7 @@ public final class aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder : a public final fun getHeaders ()Laws/smithy/kotlin/runtime/http/HeadersBuilder; public final fun getMethod ()Laws/smithy/kotlin/runtime/http/HttpMethod; public final fun getTrailingHeaders ()Laws/smithy/kotlin/runtime/http/DeferredHeadersBuilder; - public final fun getUrl ()Laws/smithy/kotlin/runtime/net/UrlBuilder; + public final fun getUrl ()Laws/smithy/kotlin/runtime/net/url/Url$Builder; public final fun setBody (Laws/smithy/kotlin/runtime/http/HttpBody;)V public final fun setMethod (Laws/smithy/kotlin/runtime/http/HttpMethod;)V public fun toString ()Ljava/lang/String; @@ -280,13 +280,13 @@ public final class aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder : a public final class aws/smithy/kotlin/runtime/http/request/HttpRequestBuilderKt { public static final fun header (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Ljava/lang/String;Ljava/lang/String;)V public static final fun headers (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Lkotlin/jvm/functions/Function1;)V - public static final fun url (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/net/Url;)V + public static final fun url (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/net/url/Url;)V public static final fun url (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Lkotlin/jvm/functions/Function1;)V } public final class aws/smithy/kotlin/runtime/http/request/HttpRequestKt { - public static final fun HttpRequest (Laws/smithy/kotlin/runtime/http/HttpMethod;Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/http/Headers;Laws/smithy/kotlin/runtime/http/HttpBody;Laws/smithy/kotlin/runtime/http/DeferredHeaders;)Laws/smithy/kotlin/runtime/http/request/HttpRequest; - public static synthetic fun HttpRequest$default (Laws/smithy/kotlin/runtime/http/HttpMethod;Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/http/Headers;Laws/smithy/kotlin/runtime/http/HttpBody;Laws/smithy/kotlin/runtime/http/DeferredHeaders;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/http/request/HttpRequest; + public static final fun HttpRequest (Laws/smithy/kotlin/runtime/http/HttpMethod;Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/http/Headers;Laws/smithy/kotlin/runtime/http/HttpBody;Laws/smithy/kotlin/runtime/http/DeferredHeaders;)Laws/smithy/kotlin/runtime/http/request/HttpRequest; + public static synthetic fun HttpRequest$default (Laws/smithy/kotlin/runtime/http/HttpMethod;Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/http/Headers;Laws/smithy/kotlin/runtime/http/HttpBody;Laws/smithy/kotlin/runtime/http/DeferredHeaders;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/http/request/HttpRequest; public static final fun toBuilder (Laws/smithy/kotlin/runtime/http/request/HttpRequest;)Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder; } diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequest.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequest.kt index ab7e70809..fb609faeb 100644 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequest.kt +++ b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequest.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.http.request import aws.smithy.kotlin.runtime.http.* -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url /** * Immutable representation of an HTTP request diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt index bbbabcba3..7d0750ea8 100644 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt +++ b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/request/HttpRequestBuilder.kt @@ -8,9 +8,7 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.content.ByteArrayContent import aws.smithy.kotlin.runtime.io.* -import aws.smithy.kotlin.runtime.net.Url -import aws.smithy.kotlin.runtime.net.UrlBuilder -import aws.smithy.kotlin.runtime.net.encodedPath +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.util.CanDeepCopy /** @@ -22,12 +20,12 @@ import aws.smithy.kotlin.runtime.util.CanDeepCopy */ public class HttpRequestBuilder private constructor( public var method: HttpMethod, - public val url: UrlBuilder, + public val url: Url.Builder, public val headers: HeadersBuilder, public var body: HttpBody, public val trailingHeaders: DeferredHeadersBuilder, ) : CanDeepCopy { - public constructor() : this(HttpMethod.GET, UrlBuilder(), HeadersBuilder(), HttpBody.Empty, DeferredHeadersBuilder()) + public constructor() : this(HttpMethod.GET, Url.Builder(), HeadersBuilder(), HttpBody.Empty, DeferredHeadersBuilder()) public fun build(): HttpRequest = HttpRequest(method, url.build(), if (headers.isEmpty()) Headers.Empty else headers.build(), body, if (trailingHeaders.isEmpty()) DeferredHeaders.Empty else trailingHeaders.build()) @@ -70,7 +68,7 @@ public fun HttpRequestBuilder.immutableView( /** * Modify the URL inside the block */ -public fun HttpRequestBuilder.url(block: UrlBuilder.() -> Unit) { +public fun HttpRequestBuilder.url(block: Url.Builder.() -> Unit) { url.apply(block) } @@ -78,16 +76,7 @@ public fun HttpRequestBuilder.url(block: UrlBuilder.() -> Unit) { * Set values from an existing [Url] instance */ public fun HttpRequestBuilder.url(value: Url) { - url.apply { - scheme = value.scheme - host = value.host - port = value.port - path = value.path - parameters.appendAll(value.parameters) - fragment = value.fragment - userInfo = value.userInfo - forceQuery = value.forceQuery - } + url.copyFrom(value) } /** @@ -113,7 +102,7 @@ public suspend fun dumpRequest(request: HttpRequestBuilder, dumpBody: Boolean): val buffer = SdkBuffer() // TODO - we have no way to know the http version at this level to set HTTP/x.x - buffer.writeUtf8("${request.method} ${request.url.encodedPath}\r\n") + buffer.writeUtf8("${request.method} ${request.url.requestRelativePath}\r\n") buffer.writeUtf8("Host: ${request.url.host}\r\n") val contentLength = request.headers["Content-Length"]?.toLongOrNull() ?: (request.body.contentLength ?: 0) diff --git a/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/HttpRequestBuilderTest.kt b/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/HttpRequestBuilderTest.kt index f6ceb3794..a57e6c53d 100644 --- a/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/HttpRequestBuilderTest.kt +++ b/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/HttpRequestBuilderTest.kt @@ -15,9 +15,8 @@ import aws.smithy.kotlin.runtime.http.request.toBuilder import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.io.SdkByteReadChannel import aws.smithy.kotlin.runtime.net.Host -import aws.smithy.kotlin.runtime.net.QueryParameters import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -50,8 +49,8 @@ class HttpRequestBuilderTest { val builder = HttpRequestBuilder().apply { url { host = Host.Domain("test.amazon.com") - path = "/debug/test" - parameters.append("foo", "bar") + path.encoded = "/debug/test" + parameters.decodedParameters.add("foo", "bar") } headers { append("x-baz", "quux") @@ -81,14 +80,12 @@ class HttpRequestBuilderTest { fun testRequestToBuilder() = runTest { val req = HttpRequest( method = HttpMethod.POST, - url = Url( - Scheme.HTTPS, - Host.Domain("test.amazon.com"), - path = "/debug/test", - parameters = QueryParameters { - append("q1", "foo") - }, - ), + url = Url { + scheme = Scheme.HTTPS + host = Host.Domain("test.amazon.com") + path.decoded = "/debug/test" + parameters.decodedParameters.add("q1", "foo") + }, headers = Headers { append("x-baz", "bar") append("x-quux", "qux") diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index 1833f3fcb..a9a3a670b 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -70,6 +70,48 @@ public final class aws/smithy/kotlin/runtime/ServiceException$ErrorType : java/l public static fun values ()[Laws/smithy/kotlin/runtime/ServiceException$ErrorType; } +public abstract interface class aws/smithy/kotlin/runtime/collections/MultiMap : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { + public abstract fun contains (Ljava/lang/Object;Ljava/lang/Object;)Z + public abstract fun getEntryValues ()Lkotlin/sequences/Sequence; + public abstract fun toMutableMultiMap ()Laws/smithy/kotlin/runtime/collections/MutableMultiMap; +} + +public final class aws/smithy/kotlin/runtime/collections/MultiMap$DefaultImpls { + public static fun contains (Laws/smithy/kotlin/runtime/collections/MultiMap;Ljava/lang/Object;Ljava/lang/Object;)Z + public static fun toMutableMultiMap (Laws/smithy/kotlin/runtime/collections/MultiMap;)Laws/smithy/kotlin/runtime/collections/MutableMultiMap; +} + +public final class aws/smithy/kotlin/runtime/collections/MultiMapKt { + public static final fun multiMapOf ([Lkotlin/Pair;)Laws/smithy/kotlin/runtime/collections/MultiMap; +} + +public abstract interface class aws/smithy/kotlin/runtime/collections/MutableMultiMap : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { + public abstract fun add (Ljava/lang/Object;ILjava/lang/Object;)V + public abstract fun add (Ljava/lang/Object;Ljava/lang/Object;)Z + public abstract fun addAll (Ljava/lang/Object;ILjava/util/Collection;)Z + public abstract fun addAll (Ljava/lang/Object;Ljava/util/Collection;)Z + public abstract fun addAll (Ljava/util/Map;)V + public abstract fun contains (Ljava/lang/Object;Ljava/lang/Object;)Z + public abstract fun getEntryValues ()Lkotlin/sequences/Sequence; + public abstract fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List; + public abstract fun removeAll (Ljava/lang/Object;Ljava/util/Collection;)Ljava/lang/Boolean; + public abstract fun removeAt (Ljava/lang/Object;I)Ljava/lang/Object; + public abstract fun removeElement (Ljava/lang/Object;Ljava/lang/Object;)Z + public abstract fun retainAll (Ljava/lang/Object;Ljava/util/Collection;)Ljava/lang/Boolean; + public abstract fun toMultiMap ()Laws/smithy/kotlin/runtime/collections/MultiMap; +} + +public final class aws/smithy/kotlin/runtime/collections/MutableMultiMap$DefaultImpls { + public static fun addAll (Laws/smithy/kotlin/runtime/collections/MutableMultiMap;Ljava/util/Map;)V + public static fun contains (Laws/smithy/kotlin/runtime/collections/MutableMultiMap;Ljava/lang/Object;Ljava/lang/Object;)Z + public static fun put (Laws/smithy/kotlin/runtime/collections/MutableMultiMap;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List; + public static fun toMultiMap (Laws/smithy/kotlin/runtime/collections/MutableMultiMap;)Laws/smithy/kotlin/runtime/collections/MultiMap; +} + +public final class aws/smithy/kotlin/runtime/collections/MutableMultiMapKt { + public static final fun mutableMultiMapOf ([Lkotlin/Pair;)Laws/smithy/kotlin/runtime/collections/MutableMultiMap; +} + public abstract interface class aws/smithy/kotlin/runtime/collections/ValuesMap { public abstract fun contains (Ljava/lang/String;)Z public abstract fun contains (Ljava/lang/String;Ljava/lang/Object;)Z @@ -630,36 +672,6 @@ public final class aws/smithy/kotlin/runtime/net/HostResolver$DefaultImpls { public static synthetic fun purgeCache$default (Laws/smithy/kotlin/runtime/net/HostResolver;Laws/smithy/kotlin/runtime/net/HostAddress;ILjava/lang/Object;)V } -public abstract interface class aws/smithy/kotlin/runtime/net/QueryParameters : aws/smithy/kotlin/runtime/collections/ValuesMap { - public static final field Companion Laws/smithy/kotlin/runtime/net/QueryParameters$Companion; -} - -public final class aws/smithy/kotlin/runtime/net/QueryParameters$Companion { - public final fun getEmpty ()Laws/smithy/kotlin/runtime/net/QueryParameters; - public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/QueryParameters; -} - -public final class aws/smithy/kotlin/runtime/net/QueryParameters$DefaultImpls { - public static fun contains (Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/String;Ljava/lang/String;)Z - public static fun forEach (Laws/smithy/kotlin/runtime/net/QueryParameters;Lkotlin/jvm/functions/Function2;)V - public static fun get (Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/String;)Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/QueryParametersBuilder : aws/smithy/kotlin/runtime/collections/ValuesMapBuilder, aws/smithy/kotlin/runtime/util/CanDeepCopy { - public fun ()V - public synthetic fun build ()Laws/smithy/kotlin/runtime/collections/ValuesMap; - public fun build ()Laws/smithy/kotlin/runtime/net/QueryParameters; - public fun deepCopy ()Laws/smithy/kotlin/runtime/net/QueryParametersBuilder; - public synthetic fun deepCopy ()Ljava/lang/Object; - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/QueryParametersKt { - public static final fun toQueryParameters (Ljava/util/Map;)Laws/smithy/kotlin/runtime/net/QueryParameters; - public static final fun urlEncode (Laws/smithy/kotlin/runtime/net/QueryParameters;)Ljava/lang/String; - public static final fun urlEncodeTo (Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/Appendable;)V -} - public final class aws/smithy/kotlin/runtime/net/Scheme { public static final field Companion Laws/smithy/kotlin/runtime/net/Scheme$Companion; public fun (Ljava/lang/String;I)V @@ -699,126 +711,9 @@ public final class aws/smithy/kotlin/runtime/net/TlsVersion : java/lang/Enum { public static fun values ()[Laws/smithy/kotlin/runtime/net/TlsVersion; } -public final class aws/smithy/kotlin/runtime/net/Url { - public static final field Companion Laws/smithy/kotlin/runtime/net/Url$Companion; - public fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILjava/lang/String;Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/String;Laws/smithy/kotlin/runtime/net/UserInfo;ZZ)V - public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILjava/lang/String;Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/String;Laws/smithy/kotlin/runtime/net/UserInfo;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Laws/smithy/kotlin/runtime/net/Scheme; - public final fun component2 ()Laws/smithy/kotlin/runtime/net/Host; - public final fun component3 ()I - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Laws/smithy/kotlin/runtime/net/QueryParameters; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Laws/smithy/kotlin/runtime/net/UserInfo; - public final fun component8 ()Z - public final fun component9 ()Z - public final fun copy (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILjava/lang/String;Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/String;Laws/smithy/kotlin/runtime/net/UserInfo;ZZ)Laws/smithy/kotlin/runtime/net/Url; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILjava/lang/String;Laws/smithy/kotlin/runtime/net/QueryParameters;Ljava/lang/String;Laws/smithy/kotlin/runtime/net/UserInfo;ZZILjava/lang/Object;)Laws/smithy/kotlin/runtime/net/Url; - public fun equals (Ljava/lang/Object;)Z - public final fun getEncodeParameters ()Z - public final fun getEncodedPath ()Ljava/lang/String; - public final fun getForceQuery ()Z - public final fun getFragment ()Ljava/lang/String; - public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; - public final fun getParameters ()Laws/smithy/kotlin/runtime/net/QueryParameters; - public final fun getPath ()Ljava/lang/String; - public final fun getPort ()I - public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; - public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/UserInfo; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/Url$Companion { - public final fun parse (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/Url; - public final fun parse (Ljava/lang/String;Laws/smithy/kotlin/runtime/net/UrlDecoding;)Laws/smithy/kotlin/runtime/net/Url; -} - -public final class aws/smithy/kotlin/runtime/net/UrlBuilder : aws/smithy/kotlin/runtime/util/CanDeepCopy { - public static final field Companion Laws/smithy/kotlin/runtime/net/UrlBuilder$Companion; - public fun ()V - public final fun build ()Laws/smithy/kotlin/runtime/net/Url; - public fun deepCopy ()Laws/smithy/kotlin/runtime/net/UrlBuilder; - public synthetic fun deepCopy ()Ljava/lang/Object; - public final fun getForceQuery ()Z - public final fun getFragment ()Ljava/lang/String; - public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; - public final fun getParameters ()Laws/smithy/kotlin/runtime/net/QueryParametersBuilder; - public final fun getPath ()Ljava/lang/String; - public final fun getPort ()Ljava/lang/Integer; - public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; - public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/UserInfo; - public final fun setForceQuery (Z)V - public final fun setFragment (Ljava/lang/String;)V - public final fun setHost (Laws/smithy/kotlin/runtime/net/Host;)V - public final fun setParameters (Laws/smithy/kotlin/runtime/net/QueryParametersBuilder;)V - public final fun setPath (Ljava/lang/String;)V - public final fun setPort (Ljava/lang/Integer;)V - public final fun setScheme (Laws/smithy/kotlin/runtime/net/Scheme;)V - public final fun setUserInfo (Laws/smithy/kotlin/runtime/net/UserInfo;)V - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/UrlBuilder$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/Url; -} - -public abstract class aws/smithy/kotlin/runtime/net/UrlDecoding { - public static final field Companion Laws/smithy/kotlin/runtime/net/UrlDecoding$Companion; - public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun contains (Laws/smithy/kotlin/runtime/net/UrlDecoding;)Z - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public final fun minus (Laws/smithy/kotlin/runtime/net/UrlDecoding;)Laws/smithy/kotlin/runtime/net/UrlDecoding; - public final fun plus (Laws/smithy/kotlin/runtime/net/UrlDecoding;)Laws/smithy/kotlin/runtime/net/UrlDecoding; - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/UrlDecoding$Companion { - public final fun getDecodeAll ()Laws/smithy/kotlin/runtime/net/UrlDecoding; - public final fun getEntries ()Ljava/util/Set; -} - -public final class aws/smithy/kotlin/runtime/net/UrlDecoding$DecodeFragment : aws/smithy/kotlin/runtime/net/UrlDecoding { - public static final field INSTANCE Laws/smithy/kotlin/runtime/net/UrlDecoding$DecodeFragment; - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/UrlDecoding$DecodeNone : aws/smithy/kotlin/runtime/net/UrlDecoding { - public static final field INSTANCE Laws/smithy/kotlin/runtime/net/UrlDecoding$DecodeNone; - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/UrlDecoding$DecodePath : aws/smithy/kotlin/runtime/net/UrlDecoding { - public static final field INSTANCE Laws/smithy/kotlin/runtime/net/UrlDecoding$DecodePath; - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/UrlDecoding$DecodeQueryParameters : aws/smithy/kotlin/runtime/net/UrlDecoding { - public static final field INSTANCE Laws/smithy/kotlin/runtime/net/UrlDecoding$DecodeQueryParameters; - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/UrlKt { - public static final fun parameters (Laws/smithy/kotlin/runtime/net/UrlBuilder;Lkotlin/jvm/functions/Function1;)V -} - -public final class aws/smithy/kotlin/runtime/net/UserInfo { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/UserInfo; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/net/UserInfo;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/net/UserInfo; - public fun equals (Ljava/lang/Object;)Z - public final fun getPassword ()Ljava/lang/String; - public final fun getUsername ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class aws/smithy/kotlin/runtime/net/url/QueryParameters : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { +public final class aws/smithy/kotlin/runtime/net/url/QueryParameters : aws/smithy/kotlin/runtime/collections/MultiMap { public static final field Companion Laws/smithy/kotlin/runtime/net/url/QueryParameters$Companion; - public synthetic fun (Ljava/util/Map;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Laws/smithy/kotlin/runtime/collections/MultiMap;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun clear ()V public fun compute (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; @@ -826,19 +721,26 @@ public final class aws/smithy/kotlin/runtime/net/url/QueryParameters : java/util public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; public fun computeIfPresent (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/function/BiFunction;)Ljava/util/List; public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; + public fun contains (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z + public synthetic fun contains (Ljava/lang/Object;Ljava/lang/Object;)Z public fun containsKey (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z public final fun containsKey (Ljava/lang/Object;)Z public final fun containsValue (Ljava/lang/Object;)Z public fun containsValue (Ljava/util/List;)Z public final fun entrySet ()Ljava/util/Set; + public fun equals (Ljava/lang/Object;)Z public fun get (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public final fun get (Ljava/lang/Object;)Ljava/util/List; + public final fun getDecodedParameters ()Laws/smithy/kotlin/runtime/collections/MultiMap; + public final fun getEncodedParameters ()Laws/smithy/kotlin/runtime/collections/MultiMap; public fun getEntries ()Ljava/util/Set; + public fun getEntryValues ()Lkotlin/sequences/Sequence; public final fun getForceQuery ()Z public fun getKeys ()Ljava/util/Set; public fun getSize ()I public fun getValues ()Ljava/util/Collection; + public fun hashCode ()I public fun isEmpty ()Z public final fun keySet ()Ljava/util/Set; public fun merge (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;Ljava/util/function/BiFunction;)Ljava/util/List; @@ -858,45 +760,76 @@ public final class aws/smithy/kotlin/runtime/net/url/QueryParameters : java/util public fun replaceAll (Ljava/util/function/BiFunction;)V public final fun size ()I public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/QueryParameters$Builder; + public fun toMutableMultiMap ()Laws/smithy/kotlin/runtime/collections/MutableMultiMap; public fun toString ()Ljava/lang/String; public final fun values ()Ljava/util/Collection; } -public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Builder : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { +public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Builder : aws/smithy/kotlin/runtime/collections/MutableMultiMap { public fun ()V + public fun add (Laws/smithy/kotlin/runtime/text/encoding/Encodable;ILaws/smithy/kotlin/runtime/text/encoding/Encodable;)V + public fun add (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z + public synthetic fun add (Ljava/lang/Object;ILjava/lang/Object;)V + public synthetic fun add (Ljava/lang/Object;Ljava/lang/Object;)Z + public fun addAll (Laws/smithy/kotlin/runtime/text/encoding/Encodable;ILjava/util/Collection;)Z + public fun addAll (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/Collection;)Z + public synthetic fun addAll (Ljava/lang/Object;ILjava/util/Collection;)Z + public synthetic fun addAll (Ljava/lang/Object;Ljava/util/Collection;)Z + public fun addAll (Ljava/util/Map;)V public final fun build ()Laws/smithy/kotlin/runtime/net/url/QueryParameters; public fun clear ()V + public fun contains (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z + public synthetic fun contains (Ljava/lang/Object;Ljava/lang/Object;)Z public fun containsKey (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z public final fun containsKey (Ljava/lang/Object;)Z public final fun containsValue (Ljava/lang/Object;)Z public fun containsValue (Ljava/util/List;)Z + public final fun copyFrom (Laws/smithy/kotlin/runtime/net/url/QueryParameters$Builder;)V + public final fun copyFrom (Laws/smithy/kotlin/runtime/net/url/QueryParameters;)V + public final fun decodedParameters (Lkotlin/jvm/functions/Function1;)V + public final fun encodedParameters (Lkotlin/jvm/functions/Function1;)V public final fun entrySet ()Ljava/util/Set; public fun get (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object; public final fun get (Ljava/lang/Object;)Ljava/util/List; public final fun getDecoded ()Ljava/lang/String; + public final fun getDecodedParameters ()Laws/smithy/kotlin/runtime/collections/MutableMultiMap; public final fun getEncoded ()Ljava/lang/String; + public final fun getEncodedParameters ()Laws/smithy/kotlin/runtime/collections/MutableMultiMap; public fun getEntries ()Ljava/util/Set; + public fun getEntryValues ()Lkotlin/sequences/Sequence; public final fun getForceQuery ()Z public fun getKeys ()Ljava/util/Set; public fun getSize ()I public fun getValues ()Ljava/util/Collection; public fun isEmpty ()Z public final fun keySet ()Ljava/util/Set; + public fun put (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public fun put (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/List;)Ljava/util/List; public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List; public fun putAll (Ljava/util/Map;)V public fun remove (Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Ljava/util/List; public final synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object; public final fun remove (Ljava/lang/Object;)Ljava/util/List; + public fun removeAll (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/Collection;)Ljava/lang/Boolean; + public synthetic fun removeAll (Ljava/lang/Object;Ljava/util/Collection;)Ljava/lang/Boolean; + public fun removeAt (Laws/smithy/kotlin/runtime/text/encoding/Encodable;I)Laws/smithy/kotlin/runtime/text/encoding/Encodable; + public synthetic fun removeAt (Ljava/lang/Object;I)Ljava/lang/Object; + public fun removeElement (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;)Z + public synthetic fun removeElement (Ljava/lang/Object;Ljava/lang/Object;)Z + public fun retainAll (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Ljava/util/Collection;)Ljava/lang/Boolean; + public synthetic fun retainAll (Ljava/lang/Object;Ljava/util/Collection;)Ljava/lang/Boolean; public final fun setDecoded (Ljava/lang/String;)V public final fun setEncoded (Ljava/lang/String;)V public final fun setForceQuery (Z)V public final fun size ()I + public fun toMultiMap ()Laws/smithy/kotlin/runtime/collections/MultiMap; public final fun values ()Ljava/util/Collection; } public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Companion { + public final fun getEmpty ()Laws/smithy/kotlin/runtime/net/url/QueryParameters; public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/QueryParameters; public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/QueryParameters; public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/QueryParameters; @@ -905,26 +838,33 @@ public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Companion { public final class aws/smithy/kotlin/runtime/net/url/Url { public static final field Companion Laws/smithy/kotlin/runtime/net/url/Url$Companion; public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILaws/smithy/kotlin/runtime/net/url/UrlPath;Laws/smithy/kotlin/runtime/net/url/QueryParameters;Laws/smithy/kotlin/runtime/net/url/UserInfo;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z public final fun getFragment ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; public final fun getParameters ()Laws/smithy/kotlin/runtime/net/url/QueryParameters; public final fun getPath ()Laws/smithy/kotlin/runtime/net/url/UrlPath; public final fun getPort ()I + public final fun getRequestRelativePath ()Ljava/lang/String; public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/url/UserInfo; + public fun hashCode ()I public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/Url$Builder; public fun toString ()Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/net/url/Url$Builder { +public final class aws/smithy/kotlin/runtime/net/url/Url$Builder : aws/smithy/kotlin/runtime/util/CanDeepCopy { public fun ()V public final fun build ()Laws/smithy/kotlin/runtime/net/url/Url; + public final fun copyFrom (Laws/smithy/kotlin/runtime/net/url/Url;)V + public fun deepCopy ()Laws/smithy/kotlin/runtime/net/url/Url$Builder; + public synthetic fun deepCopy ()Ljava/lang/Object; public final fun getDecodedFragment ()Ljava/lang/String; public final fun getEncodedFragment ()Ljava/lang/String; public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; public final fun getParameters ()Laws/smithy/kotlin/runtime/net/url/QueryParameters$Builder; public final fun getPath ()Laws/smithy/kotlin/runtime/net/url/UrlPath$Builder; public final fun getPort ()Ljava/lang/Integer; + public final fun getRequestRelativePath ()Ljava/lang/String; public final fun getScheme ()Laws/smithy/kotlin/runtime/net/Scheme; public final fun getUserInfo ()Laws/smithy/kotlin/runtime/net/url/UserInfo$Builder; public final fun parameters (Lkotlin/jvm/functions/Function1;)V @@ -939,14 +879,53 @@ public final class aws/smithy/kotlin/runtime/net/url/Url$Builder { public final class aws/smithy/kotlin/runtime/net/url/Url$Companion { public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/Url; - public final fun parse (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/Url; + public final fun parse (Ljava/lang/String;Laws/smithy/kotlin/runtime/net/url/UrlEncoding;)Laws/smithy/kotlin/runtime/net/url/Url; + public static synthetic fun parse$default (Laws/smithy/kotlin/runtime/net/url/Url$Companion;Ljava/lang/String;Laws/smithy/kotlin/runtime/net/url/UrlEncoding;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/net/url/Url; +} + +public abstract class aws/smithy/kotlin/runtime/net/url/UrlEncoding { + public static final field Companion Laws/smithy/kotlin/runtime/net/url/UrlEncoding$Companion; + public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun contains (Laws/smithy/kotlin/runtime/net/url/UrlEncoding;)Z + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun minus (Laws/smithy/kotlin/runtime/net/url/UrlEncoding;)Laws/smithy/kotlin/runtime/net/url/UrlEncoding; + public final fun plus (Laws/smithy/kotlin/runtime/net/url/UrlEncoding;)Laws/smithy/kotlin/runtime/net/url/UrlEncoding; + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/net/url/UrlEncoding$Companion { + public final fun getAll ()Laws/smithy/kotlin/runtime/net/url/UrlEncoding; + public final fun getEntries ()Ljava/util/Set; +} + +public final class aws/smithy/kotlin/runtime/net/url/UrlEncoding$Fragment : aws/smithy/kotlin/runtime/net/url/UrlEncoding { + public static final field INSTANCE Laws/smithy/kotlin/runtime/net/url/UrlEncoding$Fragment; + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/net/url/UrlEncoding$None : aws/smithy/kotlin/runtime/net/url/UrlEncoding { + public static final field INSTANCE Laws/smithy/kotlin/runtime/net/url/UrlEncoding$None; + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/net/url/UrlEncoding$Path : aws/smithy/kotlin/runtime/net/url/UrlEncoding { + public static final field INSTANCE Laws/smithy/kotlin/runtime/net/url/UrlEncoding$Path; + public fun toString ()Ljava/lang/String; +} + +public final class aws/smithy/kotlin/runtime/net/url/UrlEncoding$QueryParameters : aws/smithy/kotlin/runtime/net/url/UrlEncoding { + public static final field INSTANCE Laws/smithy/kotlin/runtime/net/url/UrlEncoding$QueryParameters; + public fun toString ()Ljava/lang/String; } public final class aws/smithy/kotlin/runtime/net/url/UrlPath { public static final field Companion Laws/smithy/kotlin/runtime/net/url/UrlPath$Companion; public synthetic fun (Ljava/util/List;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z public final fun getSegments ()Ljava/util/List; public final fun getTrailingSlash ()Z + public fun hashCode ()I public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/UrlPath$Builder; public fun toString ()Ljava/lang/String; } @@ -954,17 +933,24 @@ public final class aws/smithy/kotlin/runtime/net/url/UrlPath { public final class aws/smithy/kotlin/runtime/net/url/UrlPath$Builder { public fun ()V public final fun build ()Laws/smithy/kotlin/runtime/net/url/UrlPath; + public final fun copyFrom (Laws/smithy/kotlin/runtime/net/url/UrlPath$Builder;)V + public final fun copyFrom (Laws/smithy/kotlin/runtime/net/url/UrlPath;)V + public final fun decodedSegments (Lkotlin/jvm/functions/Function1;)V + public final fun encodedSegments (Lkotlin/jvm/functions/Function1;)V public final fun getDecoded ()Ljava/lang/String; public final fun getDecodedSegments ()Ljava/util/List; public final fun getEncoded ()Ljava/lang/String; public final fun getEncodedSegments ()Ljava/util/List; + public final fun getSegments ()Ljava/util/List; public final fun getTrailingSlash ()Z + public final fun normalize ()V public final fun setDecoded (Ljava/lang/String;)V public final fun setEncoded (Ljava/lang/String;)V public final fun setTrailingSlash (Z)V } public final class aws/smithy/kotlin/runtime/net/url/UrlPath$Companion { + public final fun getEmpty ()Laws/smithy/kotlin/runtime/net/url/UrlPath; public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/UrlPath; public final fun parseDecoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/UrlPath; public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/UrlPath; @@ -973,8 +959,12 @@ public final class aws/smithy/kotlin/runtime/net/url/UrlPath$Companion { public final class aws/smithy/kotlin/runtime/net/url/UserInfo { public static final field Companion Laws/smithy/kotlin/runtime/net/url/UserInfo$Companion; public synthetic fun (Laws/smithy/kotlin/runtime/text/encoding/Encodable;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z public final fun getPassword ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public final fun getUserName ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; + public fun hashCode ()I + public final fun isEmpty ()Z + public final fun isNotEmpty ()Z public final fun toBuilder ()Laws/smithy/kotlin/runtime/net/url/UserInfo$Builder; public fun toString ()Ljava/lang/String; } @@ -982,6 +972,8 @@ public final class aws/smithy/kotlin/runtime/net/url/UserInfo { public final class aws/smithy/kotlin/runtime/net/url/UserInfo$Builder { public fun ()V public final fun build ()Laws/smithy/kotlin/runtime/net/url/UserInfo; + public final fun copyFrom (Laws/smithy/kotlin/runtime/net/url/UserInfo$Builder;)V + public final fun copyFrom (Laws/smithy/kotlin/runtime/net/url/UserInfo;)V public final fun getDecodedPassword ()Ljava/lang/String; public final fun getDecodedUserName ()Ljava/lang/String; public final fun getEncodedPassword ()Ljava/lang/String; @@ -1432,6 +1424,8 @@ public final class aws/smithy/kotlin/runtime/text/encoding/Encodable { public final fun getEncoding ()Laws/smithy/kotlin/runtime/text/encoding/Encoding; public fun hashCode ()I public final fun isEmpty ()Z + public final fun isNotEmpty ()Z + public final fun reencode ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public fun toString ()Ljava/lang/String; } @@ -1650,16 +1644,3 @@ public final class aws/smithy/kotlin/runtime/util/StackKt { public final class aws/smithy/kotlin/runtime/util/TestPlatformProvider$Companion { } -public final class aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView : java/util/ListIterator, kotlin/jvm/internal/markers/KMutableListIterator { - public fun (Ljava/util/ListIterator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public fun add (Ljava/lang/Object;)V - public fun hasNext ()Z - public fun hasPrevious ()Z - public fun next ()Ljava/lang/Object; - public fun nextIndex ()I - public fun previous ()Ljava/lang/Object; - public fun previousIndex ()I - public fun remove ()V - public fun set (Ljava/lang/Object;)V -} - diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt new file mode 100644 index 000000000..cecf62bcc --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt @@ -0,0 +1,7 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections + +internal class Entry(override val key: K, override val value: V) : Map.Entry diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt new file mode 100644 index 000000000..69dd89b32 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections + +public interface MultiMap : Map> { + public fun contains(key: K, value: V): Boolean = get(key)?.contains(value) ?: false + + public val entryValues: Sequence> // TODO Make more dynamic, e.g. Set + + public fun toMutableMultiMap(): MutableMultiMap = + SimpleMutableMultiMap(mapValuesTo(mutableMapOf()) { (_, v) -> v.toMutableList() }) +} + +public fun multiMapOf(vararg pairs: Pair): MultiMap = + SimpleMultiMap(pairs.groupBy(Pair::first, Pair::second)) + +internal class SimpleMultiMap(private val delegate: Map>) : + MultiMap, Map> by delegate +{ + override val entryValues: Sequence> + get() = sequence { + entries.forEach { (key, values) -> + values.forEach { value -> yield(Entry(key, value)) } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as SimpleMultiMap<*, *> + + return delegate == other.delegate + } + + override fun hashCode(): Int = delegate.hashCode() + + override fun toString(): String = delegate.toString() +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt deleted file mode 100644 index a0d7635e2..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableIteratorView.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.collections - -import aws.smithy.kotlin.runtime.InternalApi - -/** - * A mutable view of a mutable iterator. This class presents the elements of a source iterator in a different - * format/type. Updates to this iterator (i.e., advancing to the next element or removing the current element) are - * propagated to the source iterator. - * @param Src The type of elements in the source iterator - * @param Dest The type of elements in this view - * @param srcIterator The source iterator containing the canonical elements - * @param srcToDest A function that transforms a [Src] object to a [Dest] - */ -@InternalApi -public class MutableIteratorView( - private val srcIterator: MutableIterator, - private val srcToDest: (Src) -> Dest, -) : MutableIterator { - override fun hasNext(): Boolean = srcIterator.hasNext() - - override fun next(): Dest = srcToDest(srcIterator.next()) - - override fun remove() { - srcIterator.remove() - } -} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt deleted file mode 100644 index d93e172db..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListIteratorView.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.collections - -import aws.smithy.kotlin.runtime.InternalApi - -/** - * A mutable view of a mutable list iterator. This class presents the elements of a source iterator in a different - * format/type. Updates to this iterator (e.g., advancing to the next element, additions, removals, etc.) are propagated - * to the source iterator. - * @param Src The type of elements in the source iterator - * @param Dest The type of elements in this view - * @param srcIterator The source iterator containing the canonical elements - * @param srcToDest A function that transforms a [Src] object to a [Dest] - * @param destToSrc A function that transforms a [Dest] object to a [Src] - */ -@InternalApi -public class MutableListIteratorView( - private val srcIterator: MutableListIterator, - private val srcToDest: (Src) -> Dest, - private val destToSrc: (Dest) -> Src, -) : MutableListIterator { - override fun add(element: Dest) { - srcIterator.add(destToSrc(element)) - } - - override fun hasNext(): Boolean = srcIterator.hasNext() - - override fun hasPrevious(): Boolean = srcIterator.hasPrevious() - - override fun next(): Dest = srcToDest(srcIterator.next()) - - override fun nextIndex(): Int = srcIterator.nextIndex() - - override fun previous(): Dest = srcToDest(srcIterator.previous()) - - override fun previousIndex(): Int = srcIterator.previousIndex() - - override fun remove() { - srcIterator.remove() - } - - override fun set(element: Dest) { - srcIterator.set(destToSrc(element)) - } -} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt deleted file mode 100644 index 05d4c68be..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableListView.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.collections - -import aws.smithy.kotlin.runtime.InternalApi - -/** - * A mutable view of a mutable list. This class presents the elements of a source list in a different format/type. - * Updates to this view (e.g., additions, removals, etc.) are propagated to the source list. - * @param Src The type of elements in the source list - * @param Dest The type of elements in this view - * @param srcList The source list containing the canonical elements - * @param srcToDest A function that transforms a [Src] object to a [Dest] - * @param destToSrc A function that transforms a [Dest] object to a [Src] - */ -@InternalApi -public class MutableListView( - private val srcList: MutableList, - private val srcToDest: (Src) -> Dest, - private val destToSrc: (Dest) -> Src, -) : MutableList { - override fun add(element: Dest): Boolean = srcList.add(destToSrc(element)) - - override fun add(index: Int, element: Dest) { - srcList.add(index, destToSrc(element)) - } - - override fun addAll(elements: Collection): Boolean = srcList.addAll(elements.map(destToSrc)) - - override fun addAll(index: Int, elements: Collection): Boolean = - srcList.addAll(index, elements.map(destToSrc)) - - override fun clear() { - srcList.clear() - } - - override fun contains(element: Dest): Boolean = srcList.contains(destToSrc(element)) - - override fun containsAll(elements: Collection): Boolean = srcList.containsAll(elements.map(destToSrc)) - - override fun get(index: Int): Dest = srcToDest(srcList[index]) - - override fun indexOf(element: Dest): Int = srcList.indexOf(destToSrc(element)) - - override fun isEmpty(): Boolean = srcList.isEmpty() - - override fun iterator(): MutableIterator = MutableIteratorView(srcList.iterator(), srcToDest) - - override fun lastIndexOf(element: Dest): Int = srcList.lastIndexOf(destToSrc(element)) - - override fun listIterator(): MutableListIterator = - MutableListIteratorView(srcList.listIterator(), srcToDest, destToSrc) - - override fun listIterator(index: Int): MutableListIterator = - MutableListIteratorView(srcList.listIterator(index), srcToDest, destToSrc) - - override fun remove(element: Dest): Boolean = srcList.remove(destToSrc(element)) - - override fun removeAll(elements: Collection): Boolean = srcList.removeAll(elements.map(destToSrc)) - - override fun removeAt(index: Int): Dest = srcToDest(srcList.removeAt(index)) - - override fun retainAll(elements: Collection): Boolean = srcList.retainAll(elements.map(destToSrc)) - - override fun set(index: Int, element: Dest): Dest = srcToDest(srcList.set(index, destToSrc(element))) - - override val size: Int - get() = srcList.size - - override fun subList(fromIndex: Int, toIndex: Int): MutableList = MutableListView( - srcList.subList(fromIndex, toIndex), - srcToDest, - destToSrc, - ) -} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt new file mode 100644 index 000000000..743faab3b --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt @@ -0,0 +1,82 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections + +import kotlin.jvm.JvmName + +// TODO can this inherit from MultiMap? +public interface MutableMultiMap : MutableMap> { + public fun add(key: K, value: V): Boolean + public fun add(key: K, index: Int, value: V) + public fun addAll(key: K, values: Collection): Boolean + public fun addAll(key: K, index: Int, values: Collection): Boolean + + public fun addAll(other: Map>) { + other.entries.forEach { (key, values) -> addAll(key, values) } + } + + public fun contains(key: K, value: V): Boolean = get(key)?.contains(value) ?: false + public val entryValues: Sequence> // TODO Make more dynamic, e.g. Set + + public fun put(key: K, value: V): MutableList? = put(key, mutableListOf(value)) + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("removeElement") + public fun remove(key: K, value: V): Boolean + + public fun removeAt(key: K, index: Int): V? + public fun removeAll(key: K, values: Collection): Boolean? + public fun retainAll(key: K, values: Collection): Boolean? + public fun toMultiMap(): MultiMap = SimpleMultiMap(mapValues { (_, v) -> v.toList() }.toMap()) +} + +public fun mutableMultiMapOf(vararg pairs: Pair): MutableMultiMap = + SimpleMutableMultiMap(pairs.groupByTo(mutableMapOf(), Pair::first, Pair::second)) + +internal class SimpleMutableMultiMap(private val delegate: MutableMap>) : + MutableMap> by delegate, MutableMultiMap +{ + private fun ensureKey(key: K) = getOrPut(key, ::mutableListOf) + + override fun add(key: K, value: V): Boolean = ensureKey(key).add(value) + + override fun add(key: K, index: Int, value: V) { + ensureKey(key).add(index, value) + } + + override fun addAll(key: K, values: Collection): Boolean = ensureKey(key).addAll(values) + + override fun addAll(key: K, index: Int, values: Collection): Boolean = ensureKey(key).addAll(index, values) + + override val entryValues: Sequence> + get() = sequence { + entries.forEach { (key, values) -> + values.forEach { value -> yield(Entry(key, value)) } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as SimpleMutableMultiMap<*, *> + + return delegate == other.delegate + } + + override fun hashCode(): Int = delegate.hashCode() + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("removeElement") + override fun remove(key: K, value: V): Boolean = this[key]?.remove(value) ?: false + + override fun removeAt(key: K, index: Int): V? = get(key)?.removeAt(index) + + override fun removeAll(key: K, values: Collection): Boolean? = get(key)?.removeAll(values) + + override fun retainAll(key: K, values: Collection): Boolean? = get(key)?.retainAll(values) + + override fun toString(): String = delegate.toString() +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt new file mode 100644 index 000000000..34ab3845c --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class CollectionView( + private val src: Collection, + private val src2Dest: (Src) -> Dest, + private val dest2Src: (Dest) -> Src, +): Collection, IterableView(src, src2Dest) { + override fun contains(element: Dest): Boolean = src.contains(dest2Src(element)) + + override fun containsAll(elements: Collection): Boolean = src.containsAll(elements.asView(dest2Src, src2Dest)) + + override fun isEmpty(): Boolean = src.isEmpty() + + override val size: Int + get() = src.size +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/Converters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/Converters.kt new file mode 100644 index 000000000..140384b03 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/Converters.kt @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +import aws.smithy.kotlin.runtime.collections.MultiMap +import aws.smithy.kotlin.runtime.collections.MutableMultiMap + +internal fun Collection.asView(src2Dest: (Src) -> Dest, dest2Src: (Dest) -> Src): Collection = + CollectionView(this, src2Dest, dest2Src) + +internal fun Iterable.asView(src2Dest: (Src) -> Dest): IterableView = + IterableView(this, src2Dest) + +internal fun Iterator.asView(src2Dest: (Src) -> Dest): IteratorView = + IteratorView(this, src2Dest) + +internal fun List.asView(src2Dest: (Src) -> Dest, dest2Src: (Dest) -> Src): ListView = + ListView(this, src2Dest, dest2Src) + +internal fun ListIterator.asView(src2Dest: (Src) -> Dest): ListIteratorView = + ListIteratorView(this, src2Dest) + +internal fun Map.asView( + kSrc2Dest: (KSrc) -> KDest, + kDest2Src: (KDest) -> KSrc, + vSrc2Dest: (VSrc) -> VDest, + vDest2Src: (VDest) -> VSrc, +) = MapView(this, kSrc2Dest, kDest2Src, vSrc2Dest, vDest2Src) + +internal fun MultiMap.asView( + kSrc2Dest: (KSrc) -> KDest, + kDest2Src: (KDest) -> KSrc, + vSrc2Dest: (VSrc) -> VDest, + vDest2Src: (VDest) -> VSrc, +) = MultiMapView(this, kSrc2Dest, kDest2Src, vSrc2Dest, vDest2Src) + +internal fun MutableCollection.asView( + src2Dest: (Src) -> Dest, + dest2Src: (Dest) -> Src, +): MutableCollectionView = MutableCollectionView(this, src2Dest, dest2Src) + +internal fun MutableIterable.asView(src2Dest: (Src) -> Dest): MutableIterableView = + MutableIterableView(this, src2Dest) + +internal fun MutableIterator.asView(src2Dest: (Src) -> Dest): MutableIteratorView = + MutableIteratorView(this, src2Dest) + +internal fun MutableList.asView( + src2Dest: (Src) -> Dest, + dest2Src: (Dest) -> Src, +): MutableListView = MutableListView(this, src2Dest, dest2Src) + +internal fun MutableListIterator.asView( + src2Dest: (Src) -> Dest, + dest2Src: (Dest) -> Src, +): MutableListIteratorView = MutableListIteratorView(this, src2Dest, dest2Src) + +internal fun MutableMap.asView( + kSrc2Dest: (KSrc) -> KDest, + kDest2Src: (KDest) -> KSrc, + vSrc2Dest: (VSrc) -> VDest, + vDest2Src: (VDest) -> VSrc, +) = MutableMapView(this, kSrc2Dest, kDest2Src, vSrc2Dest, vDest2Src) + +internal fun MutableMultiMap.asView( + kSrc2Dest: (KSrc) -> KDest, + kDest2Src: (KDest) -> KSrc, + vSrc2Dest: (VSrc) -> VDest, + vDest2Src: (VDest) -> VSrc, +) = MutableMultiMapView(this, kSrc2Dest, kDest2Src, vSrc2Dest, vDest2Src) + +internal fun MutableSet.asView( + src2Dest: (Src) -> Dest, + dest2Src: (Dest) -> Src, +): MutableSetView = MutableSetView(this, src2Dest, dest2Src) + +internal fun Set.asView(src2Dest: (Src) -> Dest, dest2Src: (Dest) -> Src): SetView = + SetView(this, src2Dest, dest2Src) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/EntryView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/EntryView.kt new file mode 100644 index 000000000..2452fd3d4 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/EntryView.kt @@ -0,0 +1,17 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class EntryView( + private val src: Map.Entry, + private val kSrc2Dest: (KSrc) -> KDest, + private val vSrc2Dest: (VSrc) -> VDest, +) : Map.Entry { + override val key: KDest + get() = kSrc2Dest(src.key) + + override val value: VDest + get() = vSrc2Dest(src.value) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IterableView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IterableView.kt new file mode 100644 index 000000000..051dfcb87 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IterableView.kt @@ -0,0 +1,12 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class IterableView( + private val src: Iterable, + private val src2Dest: (Src) -> Dest, +) : Iterable { + override fun iterator(): Iterator = src.iterator().asView(src2Dest) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IteratorView.kt new file mode 100644 index 000000000..036cb6bda --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/IteratorView.kt @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class IteratorView( + private val src: Iterator, + private val src2Dest: (Src) -> Dest, +) : Iterator { + override fun hasNext(): Boolean = src.hasNext() + + override fun next(): Dest = src2Dest(src.next()) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListIteratorView.kt new file mode 100644 index 000000000..8dc75e01f --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListIteratorView.kt @@ -0,0 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class ListIteratorView( + private val src: ListIterator, + private val src2Dest: (Src) -> Dest, +) : ListIterator, IteratorView(src, src2Dest) { + override fun hasPrevious(): Boolean = src.hasPrevious() + + override fun nextIndex(): Int = src.nextIndex() + + override fun previous(): Dest = src2Dest(src.previous()) + + override fun previousIndex(): Int = src.previousIndex() +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListView.kt new file mode 100644 index 000000000..6f9a31b3c --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/ListView.kt @@ -0,0 +1,24 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class ListView( + private val src: List, + private val src2Dest: (Src) -> Dest, + private val dest2Src: (Dest) -> Src, +) : List, CollectionView(src, src2Dest, dest2Src) { + override fun get(index: Int): Dest = src2Dest(src[index]) + + override fun indexOf(element: Dest): Int = src.indexOf(dest2Src(element)) + + override fun lastIndexOf(element: Dest): Int = src.lastIndexOf(dest2Src(element)) + + override fun listIterator(): ListIterator = src.listIterator().asView(src2Dest) + + override fun listIterator(index: Int): ListIterator = src.listIterator(index).asView(src2Dest) + + override fun subList(fromIndex: Int, toIndex: Int): List = + src.subList(fromIndex, toIndex).asView(src2Dest, dest2Src) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MapView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MapView.kt new file mode 100644 index 000000000..482fb574b --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MapView.kt @@ -0,0 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class MapView( + private val src: Map, + private val kSrc2Dest: (KSrc) -> KDest, + private val kDest2Src: (KDest) -> KSrc, + private val vSrc2Dest: (VSrc) -> VDest, + private val vDest2Src: (VDest) -> VSrc, +) : Map { + private fun fwdEntryView(src: Map.Entry) = EntryView(src, kSrc2Dest, vSrc2Dest) + private fun revEntryView(dest: Map.Entry) = EntryView(dest, kDest2Src, vDest2Src) + + override fun containsKey(key: KDest): Boolean = src.containsKey(kDest2Src(key)) + + override fun containsValue(value: VDest): Boolean = src.containsValue(vDest2Src(value)) + + override val entries: Set> + get() = src.entries.asView(::fwdEntryView, ::revEntryView) + + override fun get(key: KDest): VDest? = src[kDest2Src(key)]?.let(vSrc2Dest) + + override fun isEmpty(): Boolean = src.isEmpty() + + override val keys: Set + get() = src.keys.asView(kSrc2Dest, kDest2Src) + + override val size: Int + get() = src.size + + override val values: Collection + get() = src.values.asView(vSrc2Dest, vDest2Src) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MultiMapView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MultiMapView.kt new file mode 100644 index 000000000..2ccafbc3e --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MultiMapView.kt @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +import aws.smithy.kotlin.runtime.collections.Entry +import aws.smithy.kotlin.runtime.collections.MultiMap + +internal class MultiMapView( + private val src: MultiMap, + private val kSrc2Dest: (KSrc) -> KDest, + private val kDest2Src: (KDest) -> KSrc, + private val vSrc2Dest: (VSrc) -> VDest, + private val vDest2Src: (VDest) -> VSrc, +) : MultiMap { + private val vListSrc2Dest: (List) -> List = { it.asView(vSrc2Dest, vDest2Src) } + private val vListDest2Src: (List) -> List = { it.asView(vDest2Src, vSrc2Dest) } + + private fun fwdEntryView(src: Map.Entry>) = EntryView(src, kSrc2Dest, vListSrc2Dest) + private fun revEntryView(dest: Map.Entry>) = EntryView(dest, kDest2Src, vListDest2Src) + + override fun containsKey(key: KDest): Boolean = src.containsKey(kDest2Src(key)) + + override fun containsValue(value: List): Boolean = src.containsValue(vListDest2Src(value)) + + override val entries: Set>> + get() = src.entries.asView(::fwdEntryView, ::revEntryView) + + override fun get(key: KDest): List? = src[kDest2Src(key)]?.let(vListSrc2Dest) + + override fun isEmpty(): Boolean = src.isEmpty() + + override val entryValues: Sequence> = + src.entryValues.map { (kSrc, vSrc) -> Entry(kSrc2Dest(kSrc), vSrc2Dest(vSrc)) } + + override val keys: Set + get() = src.keys.asView(kSrc2Dest, kDest2Src) + + override val size: Int + get() = src.size + + override val values: Collection> + get() = src.values.asView(vListSrc2Dest, vListDest2Src) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableCollectionView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableCollectionView.kt new file mode 100644 index 000000000..c6d24b92c --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableCollectionView.kt @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class MutableCollectionView( + private val src: MutableCollection, + private val src2Dest: (Src) -> Dest, + private val dest2Src: (Dest) -> Src, +) : MutableCollection, CollectionView(src, src2Dest, dest2Src) { + override fun add(element: Dest): Boolean = src.add(dest2Src(element)) + + override fun addAll(elements: Collection): Boolean = src.addAll(elements.asView(dest2Src, src2Dest)) + + override fun clear() { + src.clear() + } + + override fun iterator(): MutableIterator = src.iterator().asView(src2Dest) + + override fun retainAll(elements: Collection): Boolean = src.retainAll(elements.asView(dest2Src, src2Dest)) + + override fun removeAll(elements: Collection): Boolean = src.removeAll(elements.asView(dest2Src, src2Dest)) + + override fun remove(element: Dest): Boolean = src.remove(dest2Src(element)) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableEntryView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableEntryView.kt new file mode 100644 index 000000000..cddb15dfe --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableEntryView.kt @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal class MutableEntryView( + private val src: MutableMap.MutableEntry, + private val kSrc2Dest: (KSrc) -> KDest, + private val vSrc2Dest: (VSrc) -> VDest, + private val vDest2Src: (VDest) -> VSrc, +) : MutableMap.MutableEntry, EntryView(src, kSrc2Dest, vSrc2Dest) { + override fun setValue(newValue: VDest): VDest = vSrc2Dest(src.setValue(vDest2Src(newValue))) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIterableView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIterableView.kt new file mode 100644 index 000000000..b0ae7a268 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIterableView.kt @@ -0,0 +1,12 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal class MutableIterableView( + private val src: MutableIterable, + private val src2Dest: (Src) -> Dest, +) : MutableIterable, IterableView(src, src2Dest) { + override fun iterator(): MutableIterator = src.iterator().asView(src2Dest) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIteratorView.kt new file mode 100644 index 000000000..69f68fd8a --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableIteratorView.kt @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal class MutableIteratorView( + private val src: MutableIterator, + src2Dest: (Src) -> Dest, +) : MutableIterator, IteratorView(src, src2Dest) { + override fun remove() { + src.remove() + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListIteratorView.kt new file mode 100644 index 000000000..903921fe4 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListIteratorView.kt @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class MutableListIteratorView( + private val src: MutableListIterator, + src2Dest: (Src) -> Dest, + private val dest2Src: (Dest) -> Src, +) : MutableListIterator, ListIteratorView(src, src2Dest) { + override fun add(element: Dest) { + src.add(dest2Src(element)) + } + + override fun remove() { + src.remove() + } + + override fun set(element: Dest) { + src.set(dest2Src(element)) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt new file mode 100644 index 000000000..9730e14c9 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class MutableListView( + private val src: MutableList, + private val src2Dest: (Src) -> Dest, + private val dest2Src: (Dest) -> Src, +) : MutableList, ListView(src, src2Dest, dest2Src) { + override fun add(element: Dest): Boolean = src.add(dest2Src(element)) + + override fun add(index: Int, element: Dest) { + src.add(index, dest2Src(element)) + } + + override fun addAll(index: Int, elements: Collection): Boolean = + src.addAll(index, elements.asView(dest2Src, src2Dest)) + + override fun addAll(elements: Collection): Boolean = src.addAll(elements.asView(dest2Src, src2Dest)) + + override fun clear() { + src.clear() + } + + override fun iterator(): MutableIterator = src.iterator().asView(src2Dest) + + override fun listIterator(): MutableListIterator = src.listIterator().asView(src2Dest, dest2Src) + + override fun listIterator(index: Int): MutableListIterator = + src.listIterator(index).asView(src2Dest, dest2Src) + + override fun removeAt(index: Int): Dest = src2Dest(src.removeAt(index)) + + override fun subList(fromIndex: Int, toIndex: Int): MutableList = + src.subList(fromIndex, toIndex).asView(src2Dest, dest2Src) + + override fun set(index: Int, element: Dest): Dest = src2Dest(src.set(index, dest2Src(element))) + + override fun retainAll(elements: Collection): Boolean = src.retainAll(elements.asView(dest2Src, src2Dest)) + + override fun removeAll(elements: Collection): Boolean = src.removeAll(elements.asView(dest2Src, src2Dest)) + + override fun remove(element: Dest): Boolean = src.remove(dest2Src(element)) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMapView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMapView.kt new file mode 100644 index 000000000..92033ce33 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMapView.kt @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class MutableMapView( + private val src: MutableMap, + private val kSrc2Dest: (KSrc) -> KDest, + private val kDest2Src: (KDest) -> KSrc, + private val vSrc2Dest: (VSrc) -> VDest, + private val vDest2Src: (VDest) -> VSrc, +) : MutableMap, MapView(src, kSrc2Dest, kDest2Src, vSrc2Dest, vDest2Src) { + private fun fwdEntryView(src: MutableMap.MutableEntry) = + MutableEntryView(src, kSrc2Dest, vSrc2Dest, vDest2Src) + + private fun revEntryView(dest: MutableMap.MutableEntry) = + MutableEntryView(dest, kDest2Src, vDest2Src, vSrc2Dest) + + override fun clear() { + src.clear() + } + + override val entries: MutableSet> + get() = src.entries.asView(::fwdEntryView, ::revEntryView) + + override val keys: MutableSet + get() = src.keys.asView(kSrc2Dest, kDest2Src) + + override fun put(key: KDest, value: VDest): VDest? = src.put(kDest2Src(key), vDest2Src(value))?.let(vSrc2Dest) + + override fun putAll(from: Map) { + from.entries.forEach { (kDest, vDest) -> + src[kDest2Src(kDest)] = vDest2Src(vDest) + } + + // A naive approach of src.putAll(from.asView(kDest2Src, kSrc2Dest, vDest2Src, vSrc2Dest)) doesn't work because + // [from] has a covariant key parameter and the compiler can't handle that with [kSrc2Dest]. Exact error is: + // > Type mismatch: inferred type is KDest but CapturedType(out KDest) was expected + } + + override fun remove(key: KDest): VDest? = src.remove(kDest2Src(key))?.let(vSrc2Dest) + + override val values: MutableCollection + get() = src.values.asView(vSrc2Dest, vDest2Src) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt new file mode 100644 index 000000000..433812ef4 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt @@ -0,0 +1,93 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +import aws.smithy.kotlin.runtime.collections.Entry +import aws.smithy.kotlin.runtime.collections.MutableMultiMap +import kotlin.jvm.JvmName + +// TODO is there a way to inherit [MultiMap]? Seems tricky because of the `out` param... +internal class MutableMultiMapView( + private val src: MutableMultiMap, + private val kSrc2Dest: (KSrc) -> KDest, + private val kDest2Src: (KDest) -> KSrc, + private val vSrc2Dest: (VSrc) -> VDest, + private val vDest2Src: (VDest) -> VSrc, +) : MutableMultiMap { + private val vListSrc2Dest: (MutableList) -> MutableList = { it.asView(vSrc2Dest, vDest2Src) } + private val vListDest2Src: (MutableList) -> MutableList = { it.asView(vDest2Src, vSrc2Dest) } + + private fun ensureKey(key: KDest) = getOrPut(key, ::mutableListOf) + + private fun fwdEntryView(src: MutableMap.MutableEntry>) = + MutableEntryView(src, kSrc2Dest, vListSrc2Dest, vListDest2Src) + + private fun revEntryView(dest: MutableMap.MutableEntry>) = + MutableEntryView(dest, kDest2Src, vListDest2Src, vListSrc2Dest) + + override fun add(key: KDest, value: VDest): Boolean = ensureKey(key).add(value) + + override fun add(key: KDest, index: Int, value: VDest) { + ensureKey(key).add(index, value) + } + + override fun addAll(key: KDest, values: Collection): Boolean = ensureKey(key).addAll(values) + + override fun addAll(key: KDest, index: Int, values: Collection): Boolean = + ensureKey(key).addAll(index, values) + + override fun clear() { + src.clear() + } + + override fun containsKey(key: KDest): Boolean = src.containsKey(kDest2Src(key)) + + override fun containsValue(value: MutableList): Boolean = src.containsValue(vListDest2Src(value)) + + override val entries: MutableSet>> + get() = src.entries.asView(::fwdEntryView, ::revEntryView) + + override val entryValues: Sequence> + get() = src.entryValues.map { (k, v) -> Entry(kSrc2Dest(k), vSrc2Dest(v)) } + + override fun get(key: KDest): MutableList? = src[kDest2Src(key)]?.let(vListSrc2Dest) + + override fun isEmpty(): Boolean = src.isEmpty() + + override val keys: MutableSet + get() = src.keys.asView(kSrc2Dest, kDest2Src) + + override fun put(key: KDest, value: MutableList): MutableList? = + src.put(kDest2Src(key), vListDest2Src(value))?.let(vListSrc2Dest) + + override fun putAll(from: Map>) { + from.entries.forEach { (kDest, vDest) -> + src[kDest2Src(kDest)] = vListDest2Src(vDest) + } + + // A naive approach of src.putAll(from.asView(kDest2Src, kSrc2Dest, vDest2Src, vSrc2Dest)) doesn't work because + // [from] has a covariant key parameter and the compiler can't handle that with [kSrc2Dest]. Exact error is: + // > Type mismatch: inferred type is KDest but CapturedType(out KDest) was expected + } + override fun remove(key: KDest): MutableList? = src.remove(kDest2Src(key))?.let(vListSrc2Dest) + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("removeElement") + override fun remove(key: KDest, value: VDest): Boolean = src.remove(kDest2Src(key), vDest2Src(value)) + + override fun removeAt(key: KDest, index: Int): VDest? = src.removeAt(kDest2Src(key), index)?.let(vSrc2Dest) + + override fun removeAll(key: KDest, values: Collection): Boolean? = + src.removeAll(kDest2Src(key), values.asView(vDest2Src, vSrc2Dest)) + + override fun retainAll(key: KDest, values: Collection): Boolean? = + src[kDest2Src(key)]?.retainAll(values.asView(vDest2Src, vSrc2Dest)) + + override val size: Int + get() = src.size + + override val values: MutableCollection> + get() = src.values.asView(vListSrc2Dest, vListDest2Src) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableSetView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableSetView.kt new file mode 100644 index 000000000..e006b49e7 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableSetView.kt @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class MutableSetView( + private val src: MutableSet, + private val src2Dest: (Src) -> Dest, + private val dest2Src: (Dest) -> Src, +) : MutableSet, SetView(src, src2Dest, dest2Src) { + override fun add(element: Dest): Boolean = src.add(dest2Src(element)) + + override fun addAll(elements: Collection): Boolean = src.addAll(elements.asView(dest2Src, src2Dest)) + + override fun clear() { + src.clear() + } + + override fun iterator(): MutableIterator = src.iterator().asView(src2Dest) + + override fun retainAll(elements: Collection): Boolean = src.retainAll(elements.asView(dest2Src, src2Dest)) + + override fun removeAll(elements: Collection): Boolean = src.removeAll(elements.asView(dest2Src, src2Dest)) + + override fun remove(element: Dest): Boolean = src.remove(dest2Src(element)) +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/SetView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/SetView.kt new file mode 100644 index 000000000..2dce9274f --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/SetView.kt @@ -0,0 +1,11 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +internal open class SetView( + src: Set, + src2Dest: (Src) -> Dest, + dest2Src: (Dest) -> Src, +) : Set, CollectionView(src, src2Dest, dest2Src) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt index 083ed6ff6..7c2930109 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt @@ -4,6 +4,7 @@ */ package aws.smithy.kotlin.runtime.net +/* import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.collections.ValuesMap import aws.smithy.kotlin.runtime.collections.ValuesMapBuilder @@ -109,3 +110,4 @@ public fun String.splitAsQueryParameters(): QueryParameters { } return builder.build() } +*/ diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt index 1e012a12b..778ab917d 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt @@ -4,6 +4,7 @@ */ package aws.smithy.kotlin.runtime.net +/* import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.text.encodeUrlPath import aws.smithy.kotlin.runtime.text.urlDecodeComponent @@ -170,3 +171,4 @@ public fun UserInfo(value: String): UserInfo { if (info.size > 1) info[1].urlDecodeComponent() else "", ) } +*/ diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlDecoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlDecoding.kt deleted file mode 100644 index d2c332129..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlDecoding.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net - -/** - * Identifies the type of decoding behavior desired when parsing a URL. - */ -public sealed class UrlDecoding(private val mask: Int) { - /** - * Do not URL-decode any part of the given URL string - */ - public object DecodeNone : UrlDecoding(0) { - override fun toString(): String = "DecodeNone" - } - - /** - * URL-decode the path of the given URL string - */ - public object DecodePath : UrlDecoding(1) { - override fun toString(): String = "DecodePath" - } - - /** - * URL-decode the query parameters of the given URL string - */ - public object DecodeQueryParameters : UrlDecoding(2) { - override fun toString(): String = "DecodeQueryParameters" - } - - /** - * URL-decode the fragment of the given URL string - */ - public object DecodeFragment : UrlDecoding(4) { - override fun toString(): String = "DecodeFragment" - } - - private class Composite(mask: Int) : UrlDecoding(mask) - - public operator fun plus(other: UrlDecoding): UrlDecoding = Composite(mask or other.mask) - public operator fun minus(other: UrlDecoding): UrlDecoding = Composite(mask and other.mask.inv()) - - override fun equals(other: Any?): Boolean = other is UrlDecoding && mask == other.mask - override fun hashCode(): Int = mask - override fun toString(): String = entries.filter(::contains).joinToString("|") - - public operator fun contains(item: UrlDecoding): Boolean = mask and item.mask != 0 - - public companion object { - /** - * Gets a collection of all the individual URL decoding behaviors - */ - public val entries: Set = setOf(DecodePath, DecodeQueryParameters, DecodeFragment) - - /** - * URL-decode all parts of the given URL string - */ - public val DecodeAll: UrlDecoding = Composite(entries.sumOf { it.mask }) - } -} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt index 119bf9153..3b06ecc97 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt @@ -4,6 +4,7 @@ */ package aws.smithy.kotlin.runtime.net +/* import aws.smithy.kotlin.runtime.text.splitAsQueryString import aws.smithy.kotlin.runtime.text.urlDecodeComponent @@ -111,3 +112,4 @@ internal fun String.splitHostPort(): Pair { if (hostEndIndex != -1 && hostEndIndex != length) substring(hostEndIndex + 1).toInt() else null, ) } +*/ diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt index c650026e2..c89c5fe36 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt @@ -4,6 +4,10 @@ */ package aws.smithy.kotlin.runtime.net.url +import aws.smithy.kotlin.runtime.collections.MultiMap +import aws.smithy.kotlin.runtime.collections.MutableMultiMap +import aws.smithy.kotlin.runtime.collections.mutableMultiMapOf +import aws.smithy.kotlin.runtime.collections.views.asView import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding @@ -11,15 +15,20 @@ import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding * Represents the parameters in a URL query string. */ public class QueryParameters private constructor( - private val delegate: Map>, + private val delegate: MultiMap, /** * A flag indicating whether to force inclusion of the `?` query separator even when there are no parameters (e.g., * `http://foo.com?` vs `http://foo.com`). */ public val forceQuery: Boolean, -) : Map> by delegate { +) : MultiMap by delegate { public companion object { + /** + * No query parameters + */ + public val Empty: QueryParameters = QueryParameters { } + /** * Create new [QueryParameters] via a DSL builder block * @param block The code to apply to the builder @@ -27,25 +36,22 @@ public class QueryParameters private constructor( */ public operator fun invoke(block: Builder.() -> Unit): QueryParameters = Builder().apply(block).build() - private fun asDecoded(delegate: Map>, forceQuery: Boolean) = - asString(delegate, forceQuery, Encodable::decoded) + private fun asDecoded(values: Sequence>, forceQuery: Boolean) = + asString(values, forceQuery, Encodable::decoded) - private fun asEncoded(delegate: Map>, forceQuery: Boolean) = - asString(delegate, forceQuery, Encodable::encoded) + private fun asEncoded(values: Sequence>, forceQuery: Boolean) = + asString(values, forceQuery, Encodable::encoded) private fun asString( - delegate: Map>, + values: Sequence>, forceQuery: Boolean, encodableForm: (Encodable) -> String, ) = - delegate - .entries + values .joinToString( separator = "&", - prefix = if (forceQuery || delegate.isNotEmpty()) "?" else "", - ) { (key, values) -> - values.joinToString("&") { "${encodableForm(key)}=${encodableForm(it)}" } - } + prefix = if (forceQuery || values.any()) "?" else "", + ) { (key, value) -> "${encodableForm(key)}=${encodableForm(value)}" } /** * Parse a **decoded** query string into a [QueryParameters] instance @@ -66,76 +72,146 @@ public class QueryParameters private constructor( * Copy the properties of this [QueryParameters] instance into a new [Builder] object. Any changes to the builder * *will not* affect this instance. */ - public fun toBuilder(): Builder = Builder( - delegate.mapValuesTo(mutableMapOf()) { (_, v) -> v.toMutableList() }, - forceQuery, - ) + public fun toBuilder(): Builder = Builder(delegate.toMutableMultiMap(), forceQuery) + + public val decodedParameters: MultiMap + get() = asView( + Encodable::decoded, + PercentEncoding.Query::encodableFromDecoded, + Encodable::decoded, + PercentEncoding.Query::encodableFromDecoded, + ) + + public val encodedParameters: MultiMap + get() = asView( + Encodable::encoded, + PercentEncoding.Query::encodableFromEncoded, + Encodable::encoded, + PercentEncoding.Query::encodableFromEncoded, + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as QueryParameters - override fun toString(): String = asEncoded(delegate, forceQuery) + if (delegate != other.delegate) return false + if (forceQuery != other.forceQuery) return false + + return true + } + + override fun hashCode(): Int { + var result = delegate.hashCode() + result = 31 * result + forceQuery.hashCode() + return result + } + + override fun toString(): String = asEncoded(delegate.entryValues, forceQuery) - // TODO dsl functions for access to encoded/decoded param maps, likely requires some kind of MutableMapView similar - // to MutableListView /** * A mutable builder used to construct [QueryParameters] instances */ public class Builder internal constructor( - private val delegate: MutableMap>, + private val delegate: MutableMultiMap, /** * A flag indicating whether to force inclusion of the `?` query separator even when there are no parameters * (e.g., `http://foo.com?` vs `http://foo.com`). */ public var forceQuery: Boolean = false, - ) : MutableMap> by delegate { + ) : MutableMultiMap by delegate { /** * Initialize an empty [QueryParameters] builder */ - public constructor() : this(mutableMapOf()) + public constructor() : this(mutableMultiMapOf()) /** * Get or set the query parameters as a **decoded** string. */ public var decoded: String - get() = asDecoded(delegate, forceQuery) + get() = asDecoded(delegate.entryValues, forceQuery) set(value) { parseDecoded(value) } /** * Get or set the query parameters as an **encoded** string. */ public var encoded: String - get() = asEncoded(delegate, forceQuery) + get() = asEncoded(delegate.entryValues, forceQuery) set(value) { parseEncoded(value) } - internal fun parseDecoded(decoded: String) = parse(decoded, PercentEncoding.Query::encodableFromDecoded) - internal fun parseEncoded(encoded: String) = parse(encoded, PercentEncoding.Query::encodableFromEncoded) + internal fun parse(value: String, encoding: UrlEncoding): Unit = + if (UrlEncoding.QueryParameters in encoding) parseEncoded(value) else parseDecoded(value) + + internal fun parseDecoded(decoded: String) = parseInto(decodedParameters, decoded) + internal fun parseEncoded(encoded: String) = parseInto(encodedParameters, encoded) - private fun parse(text: String, toEncodable: (String) -> Encodable) { + private fun parseInto(map: MutableMultiMap, text: String) { clear() forceQuery = text == "?" val params = text.removePrefix("?") if (params.isNotEmpty()) { - params.split("&").forEach { segment -> - val parts = segment.split("=") - val key = parts[0] - val value = when (parts.size) { - 1 -> "" - 2 -> parts[1] - else -> throw IllegalArgumentException("invalid query string segment $segment") + params + .split("&") + .map { segment -> + val parts = segment.split("=") + val key = parts[0] + val value = when (parts.size) { + 1 -> "" + 2 -> parts[1] + else -> throw IllegalArgumentException("invalid query string segment $segment") + } + key to value } - getOrPut(toEncodable(key), ::mutableListOf).add(toEncodable(value)) - } + .groupByTo(map, Pair::first, Pair::second) } } + public val decodedParameters: MutableMultiMap = asView( + Encodable::decoded, + PercentEncoding.Query::encodableFromDecoded, + Encodable::decoded, + PercentEncoding.Query::encodableFromDecoded, + ) + + public fun decodedParameters(block: MutableMultiMap.() -> Unit) { + decodedParameters.apply(block) + } + + public val encodedParameters: MutableMultiMap = asView( + Encodable::encoded, + PercentEncoding.Query::encodableFromEncoded, + Encodable::encoded, + PercentEncoding.Query::encodableFromEncoded, + ) + + public fun encodedParameters(block: MutableMultiMap.() -> Unit) { + encodedParameters.apply(block) + } + /** * Build a new [QueryParameters] from the currently-configured builder values * @return A new [QueryParameters] instance */ - public fun build(): QueryParameters = QueryParameters( - delegate.mapValues { (_, v) -> v.toList() }.toMap(), - forceQuery, - ) + public fun build(): QueryParameters = QueryParameters(delegate.toMultiMap(), forceQuery) + + public fun copyFrom(other: QueryParameters) { + clear() + other.mapValuesTo(this) { (_, values) -> + values.toMutableList() // Copy the mutable list to a new mutable list + } + forceQuery = other.forceQuery + } + + public fun copyFrom(other: Builder) { + clear() + other.mapValuesTo(this) { (_, values) -> + values.toMutableList() // Copy the mutable list to a new mutable list + } + forceQuery = other.forceQuery + } } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt index 7bf552333..5321bd754 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt @@ -11,7 +11,9 @@ import aws.smithy.kotlin.runtime.net.toUrlString import aws.smithy.kotlin.runtime.text.Scanner import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding +import aws.smithy.kotlin.runtime.text.ensurePrefix import aws.smithy.kotlin.runtime.text.urlDecodeComponent +import aws.smithy.kotlin.runtime.util.CanDeepCopy /** * Represents a full, valid URL @@ -41,16 +43,18 @@ public class Url private constructor( public operator fun invoke(block: Builder.() -> Unit): Url = Builder().apply(block).build() /** - * Parse an **encoded** string into a [Url] instance - * @param encoded An encoded URL string + * Parse a URL string into a [Url] instance + * @param value A URL string + * @param encoding The components of the given [value] which are in a URL-encoded form. Defaults to + * [UrlEncoding.All], meaning that the entire URL string is properly encoded. * @return A new [Url] instance */ - public fun parse(encoded: String): Url = Url { - val scanner = Scanner(encoded) + public fun parse(value: String, encoding: UrlEncoding = UrlEncoding.All): Url = Url { + val scanner = Scanner(value) scanner.requireAndSkip("://") { scheme = Scheme.parse(it) } scanner.optionalAndSkip("@") { - userInfo { parseEncoded(it) } + userInfo.parseEncoded(it) } scanner.upToOrEnd("/", "?", "#") { authority -> @@ -61,24 +65,70 @@ public class Url private constructor( scanner.ifStartsWith("/") { scanner.upToOrEnd("?", "#") { - path { parseEncoded("/$it") } + path.parse(it, encoding) } } scanner.ifStartsWith("?") { scanner.upToOrEnd("#") { - parameters { parseEncoded(it) } + parameters.parse(it, encoding) } } - scanner.ifStartsWith("#") { - scanner.upToOrEnd { encodedFragment = it } + scanner.ifStartsWithSkip("#") { + scanner.upToOrEnd { parseFragment(it, encoding) } } } + + private fun stringify( + scheme: Scheme, + host: Host, + port: Int, + path: UrlPath, + parameters: QueryParameters, + userInfo: UserInfo, + fragment: Encodable?, + ): Pair { + var splitAt: Int + + val encoded = buildString { + append(scheme.protocolName) + append("://") + append(userInfo) + append(host.toUrlString()) + if (port != scheme.defaultPort) { + append(":") + append(port) + } + + splitAt = length + + append(path) + append(parameters) + fragment?.let { + append('#') + append(it.encoded) + } + } + + val requestRelativePath = encoded + .substring(splitAt) + .ensurePrefix("/") // Request-line URI requires '/' for empty path + + return encoded to requestRelativePath + } } + private val encoded: String + public val requestRelativePath: String + init { require(port in 1..65535) { "Given port $port is not in required range [1, 65535]" } + + stringify(scheme, host, port, path, parameters, userInfo, fragment).let { + encoded = it.first + requestRelativePath = it.second + } } /** @@ -87,29 +137,43 @@ public class Url private constructor( */ public fun toBuilder(): Builder = Builder(this) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as Url + + if (scheme != other.scheme) return false + if (host != other.host) return false + if (port != other.port) return false + if (path != other.path) return false + if (parameters != other.parameters) return false + if (userInfo != other.userInfo) return false + if (fragment != other.fragment) return false + + return true + } + + override fun hashCode(): Int { + var result = scheme.hashCode() + result = 31 * result + host.hashCode() + result = 31 * result + port + result = 31 * result + path.hashCode() + result = 31 * result + parameters.hashCode() + result = 31 * result + userInfo.hashCode() + result = 31 * result + (fragment?.hashCode() ?: 0) + return result + } + /** * Returns the encoded string representation of this URL */ - override fun toString(): String = buildString { - append(scheme.protocolName) - append("://") - append(host.toUrlString()) - if (port != scheme.defaultPort) { - append(":") - append(port) - } - append(path) - append(parameters) - fragment?.let { - append('#') - append(it.encoded) - } - } + override fun toString(): String = encoded /** * A mutable builder used to construct [Url] instances */ - public class Builder internal constructor(url: Url?) { + public class Builder internal constructor(url: Url?) : CanDeepCopy { /** * Initialize an empty [Url] builder */ @@ -137,7 +201,8 @@ public class Url private constructor( /** * Gets the URL path builder */ - public val path: UrlPath.Builder = url?.path?.toBuilder() ?: UrlPath.Builder() + public var path: UrlPath.Builder = url?.path?.toBuilder() ?: UrlPath.Builder() + private set /** * Update the [UrlPath] of this URL via a DSL builder block @@ -181,6 +246,14 @@ public class Url private constructor( private var fragment: Encodable? = url?.fragment + internal fun parseFragment(value: String, encoding: UrlEncoding) { + if (UrlEncoding.Fragment in encoding) { + encodedFragment = value + } else { + decodedFragment = value + } + } + /** * Get or set the fragment as a **decoded** string */ @@ -210,6 +283,36 @@ public class Url private constructor( userInfo.build(), fragment, ) + + override fun deepCopy(): Builder = Builder().also { + it.scheme = scheme + it.host = host + it.port = port + it.path.copyFrom(path) + it.parameters.copyFrom(parameters) + it.userInfo.copyFrom(userInfo) + it.fragment = fragment + } + + public fun copyFrom(url: Url) { + scheme = url.scheme + host = url.host + port = url.port + path.copyFrom(url.path) + parameters.copyFrom(url.parameters) + userInfo.copyFrom(url.userInfo) + fragment = url.fragment + } + + public val requestRelativePath: String + get() = buildString { + append(path.encoded) + append(parameters.encoded) + fragment?.let { + append('#') + append(it.encoded) + } + }.ensurePrefix("/") } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlEncoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlEncoding.kt new file mode 100644 index 000000000..a5402edc4 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlEncoding.kt @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.url + +/** + * Identifies the components of a URL which are in an already-encoded form. + */ +public sealed class UrlEncoding(private val mask: Int) { + /** + * None of the encodable parts of the URL are encoded. + */ + public object None : UrlEncoding(0) { + override fun toString(): String = "None" + } + + /** + * The path of the URL is in an encoded form. + */ + public object Path : UrlEncoding(1) { + override fun toString(): String = "Path" + } + + /** + * The query parameters of the URL are in an encoded form. + */ + public object QueryParameters : UrlEncoding(2) { + override fun toString(): String = "QueryParameters" + } + + /** + * The fragment of the URL is in an encoded form. + */ + public object Fragment : UrlEncoding(4) { + override fun toString(): String = "Fragment" + } + + private class Composite(mask: Int) : UrlEncoding(mask) + + public operator fun plus(other: UrlEncoding): UrlEncoding = Composite(mask or other.mask) + public operator fun minus(other: UrlEncoding): UrlEncoding = Composite(mask and other.mask.inv()) + + override fun equals(other: Any?): Boolean = other is UrlEncoding && mask == other.mask + override fun hashCode(): Int = mask + override fun toString(): String = entries.filter(::contains).joinToString("|") + + public operator fun contains(item: UrlEncoding): Boolean = mask and item.mask != 0 + + public companion object { + /** + * Gets a collection of all the individual URL encoding behaviors + */ + public val entries: Set = setOf(Path, QueryParameters, Fragment) + + /** + * All parts of the URL are encoded. + */ + public val All: UrlEncoding = Composite(entries.sumOf(UrlEncoding::mask)) + } +} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt index 20b9e600e..3d0d798cd 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt @@ -4,9 +4,11 @@ */ package aws.smithy.kotlin.runtime.net.url -import aws.smithy.kotlin.runtime.collections.MutableListView +import aws.smithy.kotlin.runtime.collections.views.MutableListView +import aws.smithy.kotlin.runtime.collections.views.asView import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding +import aws.smithy.kotlin.runtime.util.CanDeepCopy /** * Represents the path component of a URL @@ -18,6 +20,11 @@ public class UrlPath private constructor( public val trailingSlash: Boolean = false, ) { public companion object { + /** + * No URL path + */ + public val Empty: UrlPath = UrlPath { } + /** * Create a new [UrlPath] via a DSL builder block * @param block The code to apply to the builder @@ -60,6 +67,24 @@ public class UrlPath private constructor( */ public fun toBuilder(): Builder = Builder(this) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as UrlPath + + if (segments != other.segments) return false + if (trailingSlash != other.trailingSlash) return false + + return true + } + + override fun hashCode(): Int { + var result = segments.hashCode() + result = 31 * result + trailingSlash.hashCode() + return result + } + override fun toString(): String = asEncoded(segments, trailingSlash) /** @@ -71,8 +96,6 @@ public class UrlPath private constructor( */ public constructor() : this(null) - private val segments: MutableList = path?.segments?.toMutableList() ?: mutableListOf() - /** * Get or set the URL path as a **decoded** string. */ @@ -87,29 +110,67 @@ public class UrlPath private constructor( get() = asEncoded(segments, trailingSlash) set(value) { parseEncoded(value) } + public val segments: MutableList = path?.segments?.toMutableList() ?: mutableListOf() + /** * A mutable list of **decoded** path segments. Any changes to this list will update the builder. */ - public val decodedSegments: MutableList = MutableListView( - segments, + public val decodedSegments: MutableList = segments.asView( Encodable::decoded, PercentEncoding.Query::encodableFromDecoded, ) + public fun decodedSegments(block: MutableList.() -> Unit) { + decodedSegments.apply(block) + } + /** * A mutable list of **encoded** path segments. Any changes to this list will update the builder. */ - public val encodedSegments: MutableList = MutableListView( - segments, + public val encodedSegments: MutableList = segments.asView( Encodable::encoded, PercentEncoding.Query::encodableFromEncoded, ) + public fun encodedSegments(block: MutableList.() -> Unit) { + encodedSegments.apply(block) + } + + /** + * Normalizes the segments of a URL path according to the following rules: + * * The returned path always begins with `/` (e.g., `a/b/c` → `/a/b/c`) + * * The returned path ends with `/` if the input path also does + * * Empty segments are discarded (e.g., `/a//b` → `/a/b`) + * * Segments of `.` are discarded (e.g., `/a/./b` → `/a/b`) + * * Segments of `..` are used to discard ancestor paths (e.g., `/a/b/../c` → `/a/c`) + * * All other segments are unmodified + */ + public fun normalize() { + segments.listIterator().apply { + while (hasNext()) { + when (next().decoded) { + ".", "" -> remove() + ".." -> { + remove() + check(hasPrevious()) { "Cannot normalize because \"..\" has no parent" } + previous() + remove() + } + } + } + } + + if (segments.isEmpty()) trailingSlash = true + } + /** * Indicates whether a trailing slash is present in the path (e.g., "/foo/bar/" vs "/foo/bar") */ public var trailingSlash: Boolean = path?.trailingSlash ?: false + internal fun parse(text: String, encoding: UrlEncoding): Unit = + if (UrlEncoding.Path in encoding) parseEncoded(text) else parseDecoded(text) + internal fun parseDecoded(decoded: String): Unit = parse(decoded, PercentEncoding.Path::encodableFromDecoded) internal fun parseEncoded(encoded: String): Unit = parse(encoded, PercentEncoding.Path::encodableFromEncoded) @@ -134,5 +195,17 @@ public class UrlPath private constructor( * @return A new [UrlPath] instance */ public fun build(): UrlPath = UrlPath(segments.toList(), trailingSlash) + + public fun copyFrom(other: UrlPath) { + segments.clear() + segments.addAll(other.segments) + trailingSlash = other.trailingSlash + } + + public fun copyFrom(other: Builder) { + segments.clear() + segments.addAll(other.segments) + trailingSlash = other.trailingSlash + } } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt index 400fb9e36..37c2addbf 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt @@ -41,16 +41,41 @@ public class UserInfo private constructor(public val userName: Encodable, public public fun parseEncoded(encoded: String): UserInfo = UserInfo { parseEncoded(encoded) } } + init { + require(password.isEmpty || userName.isNotEmpty) { "Cannot have a password without a user name" } + } + + public val isEmpty: Boolean = userName.isEmpty && password.isEmpty + public val isNotEmpty: Boolean = !isEmpty + /** * Copy the properties of this [UserInfo] instance into a new [Builder] object. Any changes to the builder * *will not* affect this instance. */ public fun toBuilder(): Builder = Builder(this) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as UserInfo + + if (userName != other.userName) return false + if (password != other.password) return false + + return true + } + + override fun hashCode(): Int { + var result = userName.hashCode() + result = 31 * result + password.hashCode() + return result + } + override fun toString(): String = when { userName.isEmpty -> "" password.isEmpty -> userName.encoded - else -> "${userName.encoded}:${userName.decoded}" + else -> "${userName.encoded}:${password.encoded}" } /** @@ -76,7 +101,7 @@ public class UserInfo private constructor(public val userName: Encodable, public */ public var encodedUserName: String get() = userName.encoded - set(value) { userName = PercentEncoding.UserInfo.encodableFromDecoded(value) } + set(value) { userName = PercentEncoding.UserInfo.encodableFromEncoded(value) } private var password = userInfo?.password ?: Encodable.Empty @@ -117,5 +142,15 @@ public class UserInfo private constructor(public val userName: Encodable, public * @return A new [UserInfo] instance */ public fun build(): UserInfo = UserInfo(userName, password) + + public fun copyFrom(other: UserInfo) { + userName = other.userName + password = other.password + } + + public fun copyFrom(other: Builder) { + userName = other.userName + password = other.password + } } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Scanner.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Scanner.kt index 9eb9a5c0c..f0f778614 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Scanner.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Scanner.kt @@ -21,15 +21,41 @@ public class Scanner(public val text: String) { .filter { (_, index) -> index != -1 } .minByOrNull { (_, index) -> index } + /** + * If [text] starts with [prefix] at the current position, invokes the given [handler]. Otherwise, the position does + * not change and the [handler] is not invoked. + * + * This method is similar to [ifStartsWithSkip] except that the prefix is included when [handler] is invoked. + * + * **Example**: + * + * ```kotlin + * val scanner = Scanner("abc def") + * scanner.ifStartsWith("abc") { + * println(scanner) // Prints "Scanner('abc def')" + * } + * ``` + * + * @param prefix The prefix to test for at the current position + * @param handler The handler to invoke if [text] starts with [prefix] at the current position + */ + public fun ifStartsWith(prefix: String, handler: () -> Unit) { + if (startsWith(prefix)) { + handler() + } + } + /** * If [text] starts with [prefix] at the current position, advances the current position by the length of [prefix] * and invokes the given [handler]. Otherwise, the position does not change and the [handler] is not invoked. * + * This method is similar to [ifStartsWith] except that the prefix is not included when [handler] is invoked. + * * **Example**: * * ```kotlin * val scanner = Scanner("abc def") - * scanner.ifStartsWith("abc") { + * scanner.ifStartsWithSkip("abc") { * // Scanner has now skipped "abc" * println(scanner) // Prints "Scanner(' def')" * } @@ -38,7 +64,7 @@ public class Scanner(public val text: String) { * @param prefix The prefix to test for at the current position * @param handler The handler to invoke if [text] starts with [prefix] at the current position */ - public fun ifStartsWith(prefix: String, handler: () -> Unit) { + public fun ifStartsWithSkip(prefix: String, handler: () -> Unit) { if (startsWith(prefix)) { currentIndex += prefix.length handler() diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt index 9fff2b0d9..adcdb76c4 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt @@ -221,5 +221,8 @@ public fun String.urlDecodeComponent(formUrlDecode: Boolean = false): String { public fun String.urlReencodeComponent(formUrlDecode: Boolean = false, formUrlEncode: Boolean = false): String = urlDecodeComponent(formUrlDecode).urlEncodeComponent(formUrlEncode) +@InternalApi +public fun String.ensurePrefix(prefix: String): String = if (startsWith(prefix)) this else prefix + this + @InternalApi public fun String.ensureSuffix(suffix: String): String = if (endsWith(suffix)) this else plus(suffix) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt index 192e569a6..53de00807 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt @@ -14,6 +14,7 @@ public class Encodable internal constructor( } public val isEmpty: Boolean = decoded.isEmpty() && encoded.isEmpty() + public val isNotEmpty: Boolean = !isEmpty override fun equals(other: Any?): Boolean { if (this === other) return true @@ -31,6 +32,8 @@ public class Encodable internal constructor( return result } + public fun reencode(): Encodable = encoding.encodableFromDecoded(decoded) + override fun toString(): String = buildString { append("Encodable(decoded=") append(decoded) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt index c1f4104dc..316a619a3 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt @@ -25,7 +25,7 @@ public interface Encoding { public fun encodableFromDecoded(decoded: String): Encodable = Encodable(decoded, encode(decoded), this) public fun encodableFromEncoded(encoded: String): Encodable { val decoded = decode(encoded) - val reencoded = encode(decoded) // TODO is this right? - return Encodable(decoded, reencoded, this) + // val reencoded = encode(decoded) // TODO is this right? + return Encodable(decoded, encoded, this) } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt index 719909bd3..2523d5faf 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt @@ -21,13 +21,16 @@ public class PercentEncoding( private val VALID_UCHAR = UNRESERVED + SUB_DELIMS private val VALID_PCHAR = VALID_UCHAR + setOf(':', '@') private val VALID_FCHAR = VALID_PCHAR + setOf('/', '?') - private val VALID_QCHAR = VALID_FCHAR - setOf('&', '=') + + // GOTCHA: according to RFC 3986, this _should_ be VALID_FCHAR - setOf('&', '=') but SigV4 is very strict on + // what MUST be encoded in queries: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html + private val VALID_QCHAR = UNRESERVED private const val UPPER_HEX = "0123456789ABCDEF" public val UserInfo: Encoding = PercentEncoding("user info", VALID_UCHAR) public val Path: Encoding = PercentEncoding("path", VALID_PCHAR) - public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR, mapOf(' ' to '+')) + public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR) public val Fragment: Encoding = PercentEncoding("fragment", VALID_FCHAR) private fun percentAsciiEncode(char: Char) = buildString { @@ -45,7 +48,6 @@ public class PercentEncoding( } } - @OptIn(ExperimentalStdlibApi::class) private val asciiMapping = (0..<128) .map(Int::toChar) .filterNot(validChars::contains) @@ -85,7 +87,7 @@ public class PercentEncoding( i++ } } else { - append(decodeMap[c] ?: throw IllegalArgumentException("unknown encoding, cannot decode character '$c'")) + append(decodeMap[c] ?: c) i++ } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt deleted file mode 100644 index ed0817516..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableIteratorView.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.util.newcoll - -import aws.smithy.kotlin.runtime.InternalApi - -/** - * A mutable view of a mutable iterator. This class presents the elements of a source iterator in a different - * format/type. Updates to this iterator (i.e., advancing to the next element or removing the current element) are - * propagated to the source iterator. - * @param Src The type of elements in the source iterator - * @param Dest The type of elements in this view - * @param srcIterator The source iterator containing the canonical elements - * @param srcToDest A function that transforms a [Src] object to a [Dest] - */ -@InternalApi -public class MutableIteratorView( - private val srcIterator: MutableIterator, - private val srcToDest: (Src) -> Dest, -) : MutableIterator { - override fun hasNext(): Boolean = srcIterator.hasNext() - - override fun next(): Dest = srcToDest(srcIterator.next()) - - override fun remove() { - srcIterator.remove() - } -} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt deleted file mode 100644 index 36acf7c41..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListIteratorView.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.util.newcoll - -/** - * A mutable view of a mutable list iterator. This class presents the elements of a source iterator in a different - * format/type. Updates to this iterator (e.g., advancing to the next element, additions, removals, etc.) are propagated - * to the source iterator. - * @param Src The type of elements in the source iterator - * @param Dest The type of elements in this view - * @param srcIterator The source iterator containing the canonical elements - * @param srcToDest A function that transforms a [Src] object to a [Dest] - * @param destToSrc A function that transforms a [Dest] object to a [Src] - */ -public class MutableListIteratorView( - private val srcIterator: MutableListIterator, - private val srcToDest: (Src) -> Dest, - private val destToSrc: (Dest) -> Src, -) : MutableListIterator { - override fun add(element: Dest) { - srcIterator.add(destToSrc(element)) - } - - override fun hasNext(): Boolean = srcIterator.hasNext() - - override fun hasPrevious(): Boolean = srcIterator.hasPrevious() - - override fun next(): Dest = srcToDest(srcIterator.next()) - - override fun nextIndex(): Int = srcIterator.nextIndex() - - override fun previous(): Dest = srcToDest(srcIterator.previous()) - - override fun previousIndex(): Int = srcIterator.previousIndex() - - override fun remove() { - srcIterator.remove() - } - - override fun set(element: Dest) { - srcIterator.set(destToSrc(element)) - } -} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt deleted file mode 100644 index fdcf22ffb..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/newcoll/MutableListView.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.util.newcoll - -import aws.smithy.kotlin.runtime.InternalApi - -/** - * A mutable view of a mutable list. This class presents the elements of a source list in a different format/type. - * Updates to this view (e.g., additions, removals, etc.) are propagated to the source list. - * @param Src The type of elements in the source list - * @param Dest The type of elements in this view - * @param srcList The source list containing the canonical elements - * @param srcToDest A function that transforms a [Src] object to a [Dest] - * @param destToSrc A function that transforms a [Dest] object to a [Src] - */ -@InternalApi -public class MutableListView( - private val srcList: MutableList, - private val srcToDest: (Src) -> Dest, - private val destToSrc: (Dest) -> Src, -) : MutableList { - override fun add(element: Dest): Boolean = srcList.add(destToSrc(element)) - - override fun add(index: Int, element: Dest) { - srcList.add(index, destToSrc(element)) - } - - override fun addAll(elements: Collection): Boolean = srcList.addAll(elements.map(destToSrc)) - - override fun addAll(index: Int, elements: Collection): Boolean = - srcList.addAll(index, elements.map(destToSrc)) - - override fun clear() { - srcList.clear() - } - - override fun contains(element: Dest): Boolean = srcList.contains(destToSrc(element)) - - override fun containsAll(elements: Collection): Boolean = srcList.containsAll(elements.map(destToSrc)) - - override fun get(index: Int): Dest = srcToDest(srcList[index]) - - override fun indexOf(element: Dest): Int = srcList.indexOf(destToSrc(element)) - - override fun isEmpty(): Boolean = srcList.isEmpty() - - override fun iterator(): MutableIterator = MutableIteratorView(srcList.iterator(), srcToDest) - - override fun lastIndexOf(element: Dest): Int = srcList.lastIndexOf(destToSrc(element)) - - override fun listIterator(): MutableListIterator = - MutableListIteratorView(srcList.listIterator(), srcToDest, destToSrc) - - override fun listIterator(index: Int): MutableListIterator = - MutableListIteratorView(srcList.listIterator(index), srcToDest, destToSrc) - - override fun remove(element: Dest): Boolean = srcList.remove(destToSrc(element)) - - override fun removeAll(elements: Collection): Boolean = srcList.removeAll(elements.map(destToSrc)) - - override fun removeAt(index: Int): Dest = srcToDest(srcList.removeAt(index)) - - override fun retainAll(elements: Collection): Boolean = srcList.retainAll(elements.map(destToSrc)) - - override fun set(index: Int, element: Dest): Dest = srcToDest(srcList.set(index, destToSrc(element))) - - override val size: Int - get() = srcList.size - - override fun subList(fromIndex: Int, toIndex: Int): MutableList = MutableListView( - srcList.subList(fromIndex, toIndex), - srcToDest, - destToSrc, - ) -} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt index 1d88ad21f..105ec571e 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt @@ -4,6 +4,7 @@ */ package aws.smithy.kotlin.runtime.net +/* import kotlin.test.* class QueryParametersTest { @@ -150,3 +151,4 @@ class QueryParametersTest { assertNull(params) } } +*/ \ No newline at end of file diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlDecodingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlDecodingTest.kt deleted file mode 100644 index 89d0b5923..000000000 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlDecodingTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net - -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class UrlDecodingTest { - @Test - fun testDecodeAll() { - assertTrue(UrlDecoding.DecodePath in UrlDecoding.DecodeAll) - assertTrue(UrlDecoding.DecodeQueryParameters in UrlDecoding.DecodeAll) - assertTrue(UrlDecoding.DecodeFragment in UrlDecoding.DecodeAll) - assertFalse(UrlDecoding.DecodeNone in UrlDecoding.DecodeAll) - } - - @Test - fun testPlus() { - val test = UrlDecoding.DecodePath + UrlDecoding.DecodeFragment - assertTrue(UrlDecoding.DecodePath in test) - assertFalse(UrlDecoding.DecodeQueryParameters in test) - assertTrue(UrlDecoding.DecodeFragment in test) - } - - @Test - fun testMinus() { - val test = UrlDecoding.DecodeAll - UrlDecoding.DecodeFragment - assertTrue(UrlDecoding.DecodePath in test) - assertTrue(UrlDecoding.DecodeQueryParameters in test) - assertFalse(UrlDecoding.DecodeFragment in test) - } -} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt index 23542deda..dd9816418 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt @@ -4,6 +4,7 @@ */ package aws.smithy.kotlin.runtime.net +/* import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFails @@ -192,3 +193,4 @@ class UrlTest { assertEquals("https://foo.com:1234?a=alligator&b=bear", url2) } } +*/ \ No newline at end of file diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt new file mode 100644 index 000000000..14a49a4db --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt @@ -0,0 +1,17 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.url + +import kotlin.test.Test +import kotlin.test.assertEquals + +class QueryParametersTest { + @Test + fun testParse() { + val actual = QueryParameters.parseEncoded("?") + val expected = QueryParameters { forceQuery = true } + assertEquals(expected, actual) + } +} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlEncodingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlEncodingTest.kt new file mode 100644 index 000000000..6e6186f8f --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlEncodingTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.url + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class UrlEncodingTest { + @Test + fun testDecodeAll() { + assertTrue(UrlEncoding.Path in UrlEncoding.All) + assertTrue(UrlEncoding.QueryParameters in UrlEncoding.All) + assertTrue(UrlEncoding.Fragment in UrlEncoding.All) + assertFalse(UrlEncoding.None in UrlEncoding.All) + } + + @Test + fun testPlus() { + val test = UrlEncoding.Path + UrlEncoding.Fragment + assertTrue(UrlEncoding.Path in test) + assertFalse(UrlEncoding.QueryParameters in test) + assertTrue(UrlEncoding.Fragment in test) + } + + @Test + fun testMinus() { + val test = UrlEncoding.All - UrlEncoding.Fragment + assertTrue(UrlEncoding.Path in test) + assertTrue(UrlEncoding.QueryParameters in test) + assertFalse(UrlEncoding.Fragment in test) + } +} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlParserTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt similarity index 68% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlParserTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt index 06dc0b8ae..5b3f1b721 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlParserTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt @@ -2,12 +2,14 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.net +package aws.smithy.kotlin.runtime.net.url +import aws.smithy.kotlin.runtime.net.Host +import aws.smithy.kotlin.runtime.net.Scheme +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import kotlin.test.* -import kotlin.test.Test -class UrlParserTest { +class UrlParsingTest { @Test fun testScheme() { assertEquals(Scheme.HTTP, Url.parse("http://host").scheme) @@ -142,78 +144,92 @@ class UrlParserTest { @Test fun testNoPath() { - assertEquals("", Url.parse("https://host").path) - assertEquals("", Url.parse("https://host/").path) - assertEquals("", Url.parse("https://host?").path) - assertEquals("", Url.parse("https://host#").path) - assertEquals("", Url.parse("https://host/?").path) - assertEquals("", Url.parse("https://host/#").path) - assertEquals("", Url.parse("https://host?#").path) - assertEquals("", Url.parse("https://host/?#").path) + assertEquals("", Url.parse("https://host").path.toString()) + assertEquals("", Url.parse("https://host?").path.toString()) + assertEquals("", Url.parse("https://host#").path.toString()) + assertEquals("", Url.parse("https://host?#").path.toString()) } @Test fun testPath() { - assertEquals("/path", Url.parse("https://host/path").path) - assertEquals("/path", Url.parse("https://host:80/path").path) - assertEquals("/path/suffix", Url.parse("https://host:80/path%2Fsuffix").path) - assertEquals( - "/path%2Fsuffix", - Url.parse("https://host:80/path%2Fsuffix", UrlDecoding.DecodeAll - UrlDecoding.DecodePath).path, - ) + assertEquals("/path", Url.parse("https://host/path").path.toString()) + assertEquals("/path", Url.parse("https://host:80/path").path.toString()) + assertEquals("/path%2Fsuffix", Url.parse("https://host:80/path%2Fsuffix").path.toString()) + assertEquals("/path%252Fsuffix", Url.parse("https://host:80/path%2Fsuffix", UrlEncoding.None).path.toString()) + + // Empty paths with a trailing slash + assertEquals("/", Url.parse("https://host/").path.toString()) + assertEquals("/", Url.parse("https://host/?").path.toString()) + assertEquals("/", Url.parse("https://host/#").path.toString()) + assertEquals("/", Url.parse("https://host/?#").path.toString()) } @Test fun testNoQuery() { - assertEquals(QueryParameters.Empty, Url.parse("https://host").parameters) - assertEquals(QueryParameters.Empty, Url.parse("https://host/").parameters) - assertEquals(QueryParameters.Empty, Url.parse("https://host?").parameters) - assertEquals(QueryParameters.Empty, Url.parse("https://host#").parameters) - assertEquals(QueryParameters.Empty, Url.parse("https://host/?").parameters) - assertEquals(QueryParameters.Empty, Url.parse("https://host/#").parameters) - assertEquals(QueryParameters.Empty, Url.parse("https://host?#").parameters) - assertEquals(QueryParameters.Empty, Url.parse("https://host/?#").parameters) + listOf( + "https://host", + "https://host/", + "https://host#", + "https://host/#", + ).forEach { url -> + val parsed = Url.parse(url).parameters + assertEquals(0, parsed.size) + assertFalse(parsed.forceQuery, "Expected forceQuery=false for $url") + } } @Test fun testQuery() { assertEquals( - QueryParameters { append("k", "v") }, + QueryParameters { decodedParameters { add("k", "v") } }, Url.parse("https://host?k=v").parameters, ) assertEquals( - QueryParameters { append("k", "v") }, + QueryParameters { decodedParameters { add("k", "v") } }, Url.parse("https://host/path?k=v").parameters, ) assertEquals( - QueryParameters { append("k", "v") }, + QueryParameters { decodedParameters { add("k", "v") } }, Url.parse("https://host?k=v#fragment").parameters, ) assertEquals( - QueryParameters { append("k", "v") }, + QueryParameters { decodedParameters { add("k", "v") } }, Url.parse("https://host/path?k=v#fragment").parameters, ) assertEquals( QueryParameters { - appendAll("k", listOf("v", "v")) - appendAll("k2", listOf("v 2", "v&2")) - appendAll("k 3", listOf("v3")) + decodedParameters { + addAll("k", listOf("v", "v")) + addAll("k2", listOf("v 2", "v&2")) + addAll("k 3", listOf("v3")) + } }, Url.parse("https://host/path?k=v&k=v&k2=v%202&k2=v%262&k%203=v3#fragment").parameters, ) assertEquals( QueryParameters { - appendAll("k", listOf("v", "v")) - appendAll("k2", listOf("v%202", "v%262")) - appendAll("k%203", listOf("v3")) + decodedParameters { + addAll("k", listOf("v", "v")) + addAll("k2", listOf("v%202", "v%262")) + addAll("k%203", listOf("v3")) + } }, - Url.parse( - "https://host/path?k=v&k=v&k2=v%202&k2=v%262&k%203=v3#fragment", - UrlDecoding.DecodeAll - UrlDecoding.DecodeQueryParameters, - ).parameters, + Url.parse("https://host/path?k=v&k=v&k2=v%202&k2=v%262&k%203=v3#fragment", UrlEncoding.None).parameters, ) + + // No query parameters but an empty query string (i.e., forceQuery) + listOf( + "https://host?", + "https://host/?", + "https://host?#", + "https://host/?#", + ).forEach { url -> + val parsed = Url.parse(url).parameters + assertEquals(0, parsed.size) + assertTrue(parsed.forceQuery, "Expected forceQuery=true for $url") + } } @Test @@ -221,49 +237,55 @@ class UrlParserTest { assertNull(Url.parse("https://host").fragment) assertNull(Url.parse("https://host/").fragment) assertNull(Url.parse("https://host?").fragment) - assertNull(Url.parse("https://host#").fragment) assertNull(Url.parse("https://host/?").fragment) - assertNull(Url.parse("https://host/#").fragment) - assertNull(Url.parse("https://host?#").fragment) - assertNull(Url.parse("https://host/?#").fragment) } @Test fun testFragment() { - assertEquals("frag&5ment", Url.parse("https://host#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host/#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host?#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host/?#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host/path#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host/path?#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host?k=v#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host/?k=v#frag%265ment").fragment) - assertEquals("frag&5ment", Url.parse("https://host/path?k=v#frag%265ment").fragment) + assertEquals("frag&5ment", Url.parse("https://host#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host/#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host?#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host/?#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host/path#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host/path?#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host?k=v#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host/?k=v#frag%265ment").fragment!!.decoded) + assertEquals("frag&5ment", Url.parse("https://host/path?k=v#frag%265ment").fragment!!.decoded) assertEquals( "frag%265ment", - Url.parse( - "https://host/path?k=v#frag%265ment", - UrlDecoding.DecodeAll - UrlDecoding.DecodeFragment, - ).fragment, + Url.parse("https://host/path?k=v#frag%265ment", UrlEncoding.None).fragment!!.decoded, ) + + // No fragment text but a fragment separator (i.e., `#`) + assertEquals("", Url.parse("https://host#").fragment!!.decoded) + assertEquals("", Url.parse("https://host/#").fragment!!.decoded) + assertEquals("", Url.parse("https://host?#").fragment!!.decoded) + assertEquals("", Url.parse("https://host/?#").fragment!!.decoded) } @Test fun testUserInfo() { - assertEquals(UserInfo("user:", "pass@"), Url.parse("https://user%3A:pass%40@host").userInfo) + val expected = UserInfo { + decodedUserName = "user:" + decodedPassword = "pass@" + } + assertEquals(expected, Url.parse("https://user%3A:pass%40@host").userInfo) } @Test fun testComplete() { - val expected = UrlBuilder { + val expected = Url { scheme = Scheme.HTTPS - userInfo = UserInfo("userinfo user", "userinfo pass") + userInfo { + decodedUserName = "userinfo user" + decodedPassword = "userinfo pass" + } host = Host.Domain("hostname.info") port = 4433 - path = "/pa th" - parameters.append("query", "val ue") - fragment = "frag ment" + path.decoded = "/pa th" + parameters.decodedParameters.add("query", "val ue") + decodedFragment = "frag ment" } val actual = Url.parse("https://userinfo%20user:userinfo%20pass@hostname.info:4433/pa%20th?query=val%20ue#frag%20ment") assertEquals(expected, actual) diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlPathTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlPathTest.kt new file mode 100644 index 000000000..9d6ed4530 --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlPathTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.net.url + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class UrlPathTest { + @Test + fun testTrailingSlash() { + val path = UrlPath.parseEncoded("/") + assertEquals(0, path.segments.size) + assertTrue(path.trailingSlash) + assertEquals("/", path.toString()) + } + + @Test + fun testNormalize() { + mapOf( + "" to "/", + "/" to "/", + "foo" to "/foo", + "/foo" to "/foo", + "foo/" to "/foo/", + "/foo/" to "/foo/", + "/a/b/c" to "/a/b/c", + "/a/b/../c" to "/a/c", + "/a/./c" to "/a/c", + "/./" to "/", + "/a/b/./../c" to "/a/c", + "/a/b/c/d/../e/../../f/../../../g" to "/g", + "//a//b//c//" to "/a/b/c/", + ).forEach { (unnormalized, expected) -> + val actual = UrlPath { + encoded = unnormalized + normalize() + }.toString() + + assertEquals(expected, actual, "Unexpected normalization for '$unnormalized'") + } + } + + @Test + fun testNormalizeError() { + UrlPath { + encoded = "/a/b/../../.." + + assertFailsWith { normalize() } + } + } +} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt index 25e3e7b7f..76751dc68 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt @@ -4,6 +4,7 @@ */ package aws.smithy.kotlin.runtime.net.url +/* import kotlin.test.Test import kotlin.test.fail import aws.smithy.kotlin.runtime.net.Url as OldUrl @@ -114,3 +115,4 @@ class UrlTest { ) } } +*/ diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/ScannerTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/ScannerTest.kt new file mode 100644 index 000000000..1c56232e1 --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/ScannerTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.text + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ScannerTest { + @Test + fun testUpToOrEnd() { + val text = "abc?123:xyz/789" + val scanner = Scanner(text) + + // To the '?' + scanner.upToOrEnd("?", ":", "/") { + assertEquals("abc", it) + } + + // At the '?' so no advancement, process an empty string + scanner.upToOrEnd("?", ":", "/") { + assertEquals("", it) + } + + // To the ':' + scanner.upToOrEnd(":", "/") { + assertEquals("?123", it) + } + + // To the '/' + scanner.upToOrEnd("/") { + assertEquals(":xyz", it) + } + + // To the end (because '?' doesn't appear again) + scanner.upToOrEnd("?") { + assertEquals("/789", it) + } + + // At the end, nothing more to scan + scanner.upToOrEnd("?", ":", "/") { + assertEquals("", it) + } + } +} diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/PercentEncodingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/PercentEncodingTest.kt index 84691121d..ea3658322 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/PercentEncodingTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/encoding/PercentEncodingTest.kt @@ -21,4 +21,18 @@ class PercentEncodingTest { val encoded = PercentEncoding.Query.encode(decoded) assertEquals("f%F0%9F%98%81o", encoded) } + + @Test + fun testPathDecoding() { + val encoded = "path%2Fsuffix" + val decoded = PercentEncoding.Path.decode(encoded) + assertEquals("path/suffix", decoded) + } + + @Test + fun testPathEncoding() { + val decoded = "path/suffix" + val encoded = PercentEncoding.Path.encode(decoded) + assertEquals("path%2Fsuffix", encoded) + } } diff --git a/runtime/smithy-client/api/smithy-client.api b/runtime/smithy-client/api/smithy-client.api index 002fdeb51..20014eea9 100644 --- a/runtime/smithy-client/api/smithy-client.api +++ b/runtime/smithy-client/api/smithy-client.api @@ -180,17 +180,17 @@ public final class aws/smithy/kotlin/runtime/client/SdkClientOptionKt { } public final class aws/smithy/kotlin/runtime/client/endpoints/Endpoint { - public fun (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;)V - public synthetic fun (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;)V + public synthetic fun (Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Ljava/lang/String;)V - public final fun component1 ()Laws/smithy/kotlin/runtime/net/Url; + public final fun component1 ()Laws/smithy/kotlin/runtime/net/url/Url; public final fun component2 ()Laws/smithy/kotlin/runtime/collections/ValuesMap; public final fun component3 ()Laws/smithy/kotlin/runtime/util/Attributes; - public final fun copy (Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/client/endpoints/Endpoint;Laws/smithy/kotlin/runtime/net/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; + public final fun copy (Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/client/endpoints/Endpoint;Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; public fun equals (Ljava/lang/Object;)Z public final fun getHeaders ()Laws/smithy/kotlin/runtime/collections/ValuesMap; - public final fun getUri ()Laws/smithy/kotlin/runtime/net/Url; + public final fun getUri ()Laws/smithy/kotlin/runtime/net/url/Url; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt index e63e07e5f..ab74a8936 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt @@ -7,7 +7,7 @@ package aws.smithy.kotlin.runtime.client.endpoints import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.collections.ValuesMap -import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.util.AttributeKey import aws.smithy.kotlin.runtime.util.Attributes import aws.smithy.kotlin.runtime.util.emptyAttributes diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt index b8c9a3561..16fbeead4 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt @@ -7,6 +7,7 @@ package aws.smithy.kotlin.runtime.client.endpoints.functions import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.net.* +import aws.smithy.kotlin.runtime.net.url.Url as SdkUrl import aws.smithy.kotlin.runtime.text.ensureSuffix import aws.smithy.kotlin.runtime.text.urlEncodeComponent @@ -32,9 +33,9 @@ public fun uriEncode(value: String): String = value.urlEncodeComponent(formUrlEn @InternalApi public fun parseUrl(value: String?): Url? = value?.let { - val sdkUrl: aws.smithy.kotlin.runtime.net.Url + val sdkUrl: SdkUrl try { - sdkUrl = aws.smithy.kotlin.runtime.net.Url.parse(value) + sdkUrl = SdkUrl.parse(value) } catch (e: Exception) { return null } @@ -46,11 +47,12 @@ public fun parseUrl(value: String?): Url? = } } + val sdkUrlPath = sdkUrl.path.toString() return Url( scheme = sdkUrl.scheme.protocolName, authority, - path = sdkUrl.path, - normalizedPath = sdkUrl.path.ensureSuffix("/"), + path = sdkUrlPath, + normalizedPath = sdkUrlPath.ensureSuffix("/"), isIp = sdkUrl.host is Host.IpAddress, ) } diff --git a/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt b/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt index 409342aa7..bb672aea1 100644 --- a/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt +++ b/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt @@ -10,7 +10,6 @@ import aws.smithy.kotlin.runtime.http.HttpMethod import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.httptest.TestEngine -import aws.smithy.kotlin.runtime.text.urlEncodeComponent import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.test.runTest import kotlin.test.assertEquals @@ -86,21 +85,22 @@ public fun httpRequestTest(block: HttpRequestTestBuilder.() -> Unit): TestResult private suspend fun assertRequest(expected: ExpectedHttpRequest, actual: HttpRequest) { // run the assertions assertEquals(expected.method, actual.method, "expected method: `${expected.method}`; got: `${actual.method}`") - assertEquals(expected.uri, actual.url.path, "expected path: `${expected.uri}`; got: `${actual.url.path}`") + assertEquals(expected.uri, actual.url.path.toString(), "expected path: `${expected.uri}`; got: `${actual.url.path}`") - // have to deal with URL encoding - expected.queryParams.forEach { (name, value) -> - val actualValues = actual.url.parameters.getAll(name) - assertNotNull(actualValues, "expected query parameter `$name`; no values found") - assertTrue(actualValues.map { it.urlEncodeComponent() }.contains(value), "expected query name value pair not found: `$name:$value`") + val actualParams = actual.url.parameters.encodedParameters + + expected.queryParams.forEach { (key, value) -> + val actualValues = actualParams[key] + assertNotNull(actualValues, "expected query parameter `$key`; no values found") + assertTrue(value in actualValues, "expected query name value pair not found: `$key:$value`") } expected.forbiddenQueryParams.forEach { - assertFalse(actual.url.parameters.contains(it), "forbidden query parameter found: `$it`") + assertFalse(it in actualParams, "forbidden query parameter found: `$it`") } expected.requiredQueryParams.forEach { - assertTrue(actual.url.parameters.contains(it), "expected required query parameter not found: `$it`") + assertTrue(it in actualParams, "expected required query parameter not found: `$it`") } expected.headers.forEach { (name, value) -> diff --git a/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt b/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt index 2730efddc..9f02bfd78 100644 --- a/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt +++ b/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt @@ -8,6 +8,7 @@ import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.HttpMethod import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.headers +import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.operation.ExecutionContext import io.kotest.matchers.string.shouldContain @@ -47,7 +48,7 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val builder = HttpRequest { method = HttpMethod.POST - url.path = "/bar" + url.path.encoded = "/bar" } mockEngine.roundTrip(execContext, builder) } @@ -68,9 +69,14 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val request = HttpRequest { method = HttpMethod.POST - url.path = "/foo" - url.parameters.append("baz", "quux") - url.parameters.append("Hi", "Hello") + + url { + path.encoded = "/foo" + parameters.decodedParameters { + add("baz", "quux") + add("Hi", "Hello") + } + } } mockEngine.roundTrip(execContext, request) } @@ -92,10 +98,15 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val request = HttpRequest { method = HttpMethod.POST - url.path = "/foo" - url.parameters.append("baz", "quux") - url.parameters.append("Hi", "Hello there") - url.parameters.append("foobar", "i am forbidden") + + url { + path.encoded = "/foo" + parameters.decodedParameters { + add("baz", "quux") + add("Hi", "Hello there") + add("foobar", "i am forbidden") + } + } } mockEngine.roundTrip(execContext, request) } @@ -118,10 +129,15 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val request = HttpRequest { method = HttpMethod.POST - url.path = "/foo" - url.parameters.append("baz", "quux") - url.parameters.append("Hi", "Hello there") - url.parameters.append("foobar2", "i am not forbidden") + + url { + path.encoded = "/foo" + parameters.decodedParameters { + add("baz", "quux") + add("Hi", "Hello there") + add("foobar2", "i am not forbidden") + } + } } mockEngine.roundTrip(execContext, request) } @@ -148,11 +164,16 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val request = HttpRequest { method = HttpMethod.POST - url.path = "/foo" - url.parameters.append("baz", "quux") - url.parameters.append("Hi", "Hello there") - url.parameters.append("foobar2", "i am not forbidden") - url.parameters.append("requiredQuery", "i am required") + + url { + path.encoded = "/foo" + parameters.decodedParameters { + add("baz", "quux") + add("Hi", "Hello there") + add("foobar2", "i am not forbidden") + add("requiredQuery", "i am required") + } + } headers { append("k1", "v1") @@ -180,7 +201,7 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val request = HttpRequest { method = HttpMethod.POST - url.path = "/foo" + url.path.encoded = "/foo" headers { appendAll("k1", listOf("v1", "v2")) appendAll("k2", listOf("v3", "v4")) @@ -212,11 +233,16 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val request = HttpRequest { method = HttpMethod.POST - url.path = "/foo" - url.parameters.append("baz", "quux") - url.parameters.append("Hi", "Hello there") - url.parameters.append("foobar2", "i am not forbidden") - url.parameters.append("requiredQuery", "i am required") + + url{ + path.encoded = "/foo" + parameters.decodedParameters { + add("baz", "quux") + add("Hi", "Hello there") + add("foobar2", "i am not forbidden") + add("requiredQuery", "i am required") + } + } headers { append("k1", "v1") @@ -251,11 +277,16 @@ class HttpRequestTestBuilderTest { operation { mockEngine -> val request = HttpRequest { method = HttpMethod.POST - url.path = "/foo" - url.parameters.append("baz", "quux") - url.parameters.append("Hi", "Hello there") - url.parameters.append("foobar2", "i am not forbidden") - url.parameters.append("requiredQuery", "i am required") + + url { + path.encoded = "/foo" + parameters.decodedParameters { + add("baz", "quux") + add("Hi", "Hello there") + add("foobar2", "i am not forbidden") + add("requiredQuery", "i am required") + } + } headers { append("k1", "v1") diff --git a/tests/benchmarks/aws-signing-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/auth/signing/AwsSignerBenchmark.kt b/tests/benchmarks/aws-signing-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/auth/signing/AwsSignerBenchmark.kt index cab570ee7..2b19713fe 100644 --- a/tests/benchmarks/aws-signing-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/auth/signing/AwsSignerBenchmark.kt +++ b/tests/benchmarks/aws-signing-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/auth/signing/AwsSignerBenchmark.kt @@ -34,7 +34,7 @@ private val requestToSign = HttpRequest { url { scheme = Scheme.HTTPS host = Host.Domain("foo.com") - path = "bar/baz/../qux/" + path.decoded = "bar/baz/../qux/" port = 8080 } headers { diff --git a/tests/benchmarks/http-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/http/HttpEngineBenchmarks.kt b/tests/benchmarks/http-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/http/HttpEngineBenchmarks.kt index 667350f79..0dfe3161f 100644 --- a/tests/benchmarks/http-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/http/HttpEngineBenchmarks.kt +++ b/tests/benchmarks/http-benchmarks/jvm/src/aws/smithy/kotlin/benchmarks/http/HttpEngineBenchmarks.kt @@ -86,7 +86,7 @@ open class HttpEngineBenchmarks { scheme = Scheme.HTTP host = Host.Domain("localhost") port = serverPort - path = "/hello" + path.decoded = "/hello" } headers { @@ -99,7 +99,7 @@ open class HttpEngineBenchmarks { scheme = Scheme.HTTP host = Host.Domain("localhost") port = serverPort - path = "/download" + path.decoded = "/download" } headers { @@ -113,7 +113,7 @@ open class HttpEngineBenchmarks { method = HttpMethod.POST host = Host.Domain("localhost") port = serverPort - path = "/upload" + path.decoded = "/upload" } body = HttpBody.fromBytes(largeData) } @@ -125,7 +125,7 @@ open class HttpEngineBenchmarks { method = HttpMethod.POST host = Host.Domain("localhost") port = serverPort - path = "/upload" + path.decoded = "/upload" } body = if (useSource) { object : HttpBody.SourceContent() { From 4720935eeab2aa8966c76ec3cbd470f43c394d8f Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Fri, 17 Nov 2023 05:13:03 +0000 Subject: [PATCH 06/17] Remove now-unnecessary text utilities --- .../runtime/auth/awssigning/Canonicalizer.kt | 1 - runtime/protocol/http/api/http.api | 3 - .../smithy/kotlin/runtime/http/util/Text.kt | 34 --- .../kotlin/runtime/http/util/TextTest.kt | 36 --- .../src/aws/smithy/kotlin/runtime/net/Host.kt | 13 +- .../kotlin/runtime/net/QueryParameters.kt | 113 --------- .../src/aws/smithy/kotlin/runtime/net/Text.kt | 14 -- .../src/aws/smithy/kotlin/runtime/net/Url.kt | 174 -------------- .../smithy/kotlin/runtime/net/UrlParser.kt | 115 ---------- .../aws/smithy/kotlin/runtime/net/url/Url.kt | 55 ++--- .../aws/smithy/kotlin/runtime/text/Text.kt | 214 ------------------ .../runtime/text/encoding/PercentEncoding.kt | 1 + .../smithy/kotlin/runtime/text/TextTest.kt | 174 -------------- .../serde/formurl/FormUrlSerializer.kt | 12 +- .../client/endpoints/functions/Functions.kt | 4 +- 15 files changed, 38 insertions(+), 925 deletions(-) delete mode 100644 runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt delete mode 100644 runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt delete mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt delete mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/TextTest.kt diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt index ab25388ed..e941da8e0 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt @@ -18,7 +18,6 @@ import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.net.url.UrlPath import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import aws.smithy.kotlin.runtime.text.encoding.encodeToHex -import aws.smithy.kotlin.runtime.text.urlEncodeComponent import aws.smithy.kotlin.runtime.time.TimestampFormat import kotlinx.coroutines.withContext diff --git a/runtime/protocol/http/api/http.api b/runtime/protocol/http/api/http.api index 8cc273d2e..4a9d4c0e2 100644 --- a/runtime/protocol/http/api/http.api +++ b/runtime/protocol/http/api/http.api @@ -309,6 +309,3 @@ public final class aws/smithy/kotlin/runtime/http/response/HttpResponseKt { public final class aws/smithy/kotlin/runtime/http/util/HeaderListsKt { } -public final class aws/smithy/kotlin/runtime/http/util/TextKt { -} - diff --git a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt b/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt deleted file mode 100644 index 43d5e6aa2..000000000 --- a/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/util/Text.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.http.util - -import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.text.VALID_PCHAR_DELIMS -import aws.smithy.kotlin.runtime.text.encodeUrlPath - -// RFC-3986 §3.3 allows sub-delims (defined in section2.2) to be in the path component. -// This includes both colon ':' and comma ',' characters. -// Smithy protocol tests & AWS services percent encode these expected values. Signing -// will fail if these values are not percent encoded -private val VALID_HTTP_LABEL_DELIMS: Set = VALID_PCHAR_DELIMS - "/ :,?#[]()@!$&'*+;=%".toSet() - -private val GREEDY_HTTP_LABEL_DELIMS: Set = VALID_HTTP_LABEL_DELIMS + '/' - -/** - * Encode a value that represents a member bound via `httpLabel` - * See: https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#httplabel-trait - * - * @param greedy Flag indicating this label is "greedy" (which allows for the value to have path separators in it) - * See: https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#greedy-labels - */ -@InternalApi -public fun String.encodeLabel(greedy: Boolean = false): String { - val validDelims = if (greedy) { - GREEDY_HTTP_LABEL_DELIMS - } else { - VALID_HTTP_LABEL_DELIMS - } - return encodeUrlPath(validDelims, checkPercentEncoded = false) -} diff --git a/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt b/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt deleted file mode 100644 index 7d8b87280..000000000 --- a/runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/util/TextTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.http.util - -import aws.smithy.kotlin.runtime.text.urlEncodeComponent -import kotlin.test.* - -class TextTest { - @Test - fun encodeLabels() { - assertEquals("a%2Fb", "a/b".encodeLabel()) - assertEquals("a/b", "a/b".encodeLabel(greedy = true)) - } - - @Test - fun encodeReservedChars() { - // verify that both httpLabel and httpQuery bound components encode characters from the reserved - // set of characters in section 2.2 - val input = ":/?#[]@!$&'()*+,;=% " - val expected = "%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D%25%20" - assertEquals(expected, input.encodeLabel()) - assertEquals(expected, input.urlEncodeComponent()) - } - - @Test - fun encodesPercent() { - // verify that something that looks percent encoded actually encodes the percent. label/query should always - // be going from raw -> encoded. Users should not be percent-encoding values passed to the runtime - val input = "%25" - val expected = "%2525" - assertEquals(expected, input.encodeLabel()) - assertEquals(expected, input.urlEncodeComponent()) - } -} diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt index afc1d7ee6..b2db01566 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Host.kt @@ -4,7 +4,7 @@ */ package aws.smithy.kotlin.runtime.net -import aws.smithy.kotlin.runtime.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding /** * A [Host] represents a parsed internet host. This may be an internet address (IPv4, IPv6) or a domain name. @@ -39,13 +39,10 @@ private fun hostParseImpl(host: String): Host { public fun Host.toUrlString(): String = when (this) { is Host.IpAddress -> when (address) { - is IpV6Addr -> { - if (address.zoneId == null) { - "[$address]" - } else { - val withoutZoneId = address.copy(zoneId = null) - "[$withoutZoneId%25${address.zoneId.urlEncodeComponent()}]" - } + is IpV6Addr -> buildString { + append('[') + append(PercentEncoding.Host.encode(address.toString())) + append(']') } else -> address.toString() } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt deleted file mode 100644 index 7c2930109..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/QueryParameters.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net - -/* -import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.collections.ValuesMap -import aws.smithy.kotlin.runtime.collections.ValuesMapBuilder -import aws.smithy.kotlin.runtime.collections.ValuesMapImpl -import aws.smithy.kotlin.runtime.collections.deepCopy -import aws.smithy.kotlin.runtime.text.splitAsQueryString -import aws.smithy.kotlin.runtime.text.urlEncodeComponent -import aws.smithy.kotlin.runtime.util.CanDeepCopy - -/** - * Container for HTTP query parameters - */ -public interface QueryParameters : ValuesMap { - public companion object { - public operator fun invoke(block: QueryParametersBuilder.() -> Unit): QueryParameters = QueryParametersBuilder() - .apply(block).build() - - /** - * Empty [QueryParameters] instance - */ - public val Empty: QueryParameters = EmptyQueryParameters - } -} - -private object EmptyQueryParameters : QueryParameters { - override val caseInsensitiveName: Boolean = true - override fun getAll(name: String): List = emptyList() - override fun names(): Set = emptySet() - override fun entries(): Set>> = emptySet() - override fun contains(name: String): Boolean = false - override fun isEmpty(): Boolean = true -} - -public class QueryParametersBuilder : ValuesMapBuilder(true, 8), CanDeepCopy { - override fun toString(): String = "QueryParametersBuilder ${entries()} " - override fun build(): QueryParameters = QueryParametersImpl(values) - override fun deepCopy(): QueryParametersBuilder { - val originalValues = values.deepCopy() - return QueryParametersBuilder().apply { values.putAll(originalValues) } - } -} - -public fun Map.toQueryParameters(): QueryParameters { - val builder = QueryParametersBuilder() - entries.forEach { entry -> builder.append(entry.key, entry.value) } - return builder.build() -} - -private class QueryParametersImpl(values: Map> = emptyMap()) : QueryParameters, ValuesMapImpl(true, values) { - override fun toString(): String = "QueryParameters ${entries()}" - - override fun equals(other: Any?): Boolean = other is QueryParameters && entries() == other.entries() -} - -/** - * Return the encoded query parameter string (without leading '?') - */ -public fun QueryParameters.urlEncode(): String = buildString { - urlEncodeTo(this) -} - -/** - * URL encode the query parameters components to the appendable output (without the leading '?') - */ -public fun QueryParameters.urlEncodeTo(out: Appendable): Unit = urlEncodeQueryParametersTo(entries(), out) - -internal fun urlEncodeQueryParametersTo(entries: Set>>, out: Appendable, encodeFn: (String) -> String = { it.urlEncodeComponent() }) { - entries.sortedBy { it.key }.forEachIndexed { i, entry -> - entry.value.forEachIndexed { j, value -> - if (i > 0 || j > 0) { - out.append("&") - } - // FIXME keys should be %-encoded - out.append(entry.key) - out.append("=") - out.append(encodeFn(value)) - } - } -} - -/** - * Parse a set of [QueryParameters] out of a full URI. If the URI does not contain a `?` (or contains nothing after the - * `?`) then the result is null. - */ -@InternalApi -public fun CharSequence.fullUriToQueryParameters(): QueryParameters? { - val idx = indexOf("?") - if (idx < 0 || idx + 1 >= length) return null - - val fragmentIdx = indexOf("#", startIndex = idx) - val rawQueryString = if (fragmentIdx > 0) substring(idx + 1, fragmentIdx) else substring(idx + 1) - return rawQueryString.splitAsQueryParameters() -} - -/** - * Split a (decoded) query string "foo=baz&bar=quux" into it's component parts - */ -@InternalApi -public fun String.splitAsQueryParameters(): QueryParameters { - val builder = QueryParametersBuilder() - splitAsQueryString().entries.forEach { entry -> - builder.appendAll(entry.key, entry.value) - } - return builder.build() -} -*/ diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Text.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Text.kt index 3b23ece56..401518ccf 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Text.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Text.kt @@ -111,18 +111,4 @@ private fun String.isIpv6AddressSegment(): Boolean = length in 1..4 && all(Char: internal fun String.isIpv6ZoneId(): Boolean = isNotEmpty() && '%' !in this -private fun String.isValidPercentEncoded(): Boolean { - forEachIndexed { index, char -> - when (char) { - in 'a'..'z', in 'A'..'Z', in '0'..'9', '-', '_', '.', '~' -> return@forEachIndexed - '%' -> { - if (index > length - 2) return false - if (!this[index+1].isHexDigit() || !this[index+2].isHexDigit()) return false - } - else -> return false - } - } - return true -} - private fun Char.isHexDigit(): Boolean = this in '0'..'9' || this in 'a'..'f' || this in 'A'..'F' diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt deleted file mode 100644 index 778ab917d..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/Url.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net - -/* -import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.text.encodeUrlPath -import aws.smithy.kotlin.runtime.text.urlDecodeComponent -import aws.smithy.kotlin.runtime.text.urlEncodeComponent -import aws.smithy.kotlin.runtime.util.CanDeepCopy - -/** - * Represents an immutable URL of the form: `scheme://[userinfo@]host[:port][/path][?query][#fragment]` - * - * @property scheme The wire protocol (e.g. http, https, ws, wss, etc) - * @property host hostname - * @property port port to connect to the host on, defaults to [Scheme.defaultPort] - * @property path (raw) path without the query - * @property parameters (raw) query parameters - * @property fragment (raw) URL fragment - * @property userInfo username and password (optional) - * @property forceQuery keep trailing question mark regardless of whether there are any query parameters - * @property encodeParameters configures if parameter values are encoded (default) or left as-is. - */ -public data class Url( - public val scheme: Scheme, - public val host: Host, - public val port: Int = scheme.defaultPort, - public val path: String = "", - public val parameters: QueryParameters = QueryParameters.Empty, - public val fragment: String? = null, - public val userInfo: UserInfo? = null, - public val forceQuery: Boolean = false, - public val encodeParameters: Boolean = true, -) { - init { - require(port in 1..65535) { "Given port $port is not in required range [1, 65535]" } - } - - public companion object { - public fun parse(url: String): Url = parse(url, UrlDecoding.DecodeAll) - public fun parse(url: String, decodingBehavior: UrlDecoding): Url = urlParseImpl(url, decodingBehavior) - } - - override fun toString(): String = buildString { - append(scheme.protocolName) - append("://") - userInfo?.let { userinfo -> - if (userinfo.username.isNotBlank()) { - append(userinfo.username.urlEncodeComponent()) - if (userinfo.password.isNotBlank()) { - append(":${userinfo.password.urlEncodeComponent()}") - } - append("@") - } - } - - append(host.toUrlString()) - if (port != scheme.defaultPort) { - append(":$port") - } - - append(encodedPath) - } - - /** - * Get the full encoded path including query parameters and fragment - */ - public val encodedPath: String - get() = encodePath(path, parameters.entries(), fragment, forceQuery, encodeParameters) -} - -// get the full encoded URL path component e.g. `/path/foo/bar?x=1&y=2#fragment` -private fun encodePath( - path: String, - queryParameters: Set>>? = null, - fragment: String? = null, - forceQuery: Boolean = false, - encodeParameters: Boolean = true, -): String = buildString { - if (path.isNotBlank()) { - append("/") - append(path.removePrefix("/").encodeUrlPath()) - } - - if (!queryParameters.isNullOrEmpty() || forceQuery) { - append("?") - } - - if (encodeParameters) { - queryParameters?.let { urlEncodeQueryParametersTo(it, this) } - } else { - queryParameters?.let { urlEncodeQueryParametersTo(it, this, encodeFn = { param -> param }) } - } - - if (!fragment.isNullOrBlank()) { - append("#") - append(fragment.urlEncodeComponent()) - } -} - -/** - * URL username and password - */ -public data class UserInfo(public val username: String, public val password: String) - -/** - * Construct a URL by its individual components - */ -public class UrlBuilder : CanDeepCopy { - public var scheme: Scheme = Scheme.HTTPS - public var host: Host = Host.Domain("") - public var port: Int? = null - public var path: String = "" - public var parameters: QueryParametersBuilder = QueryParametersBuilder() - public var fragment: String? = null - public var userInfo: UserInfo? = null - public var forceQuery: Boolean = false - - public companion object { - public operator fun invoke(block: UrlBuilder.() -> Unit): Url = UrlBuilder().apply(block).build() - } - - public fun build(): Url = Url( - scheme, - host, - port ?: scheme.defaultPort, - path, - if (parameters.isEmpty()) QueryParameters.Empty else parameters.build(), - fragment, - userInfo, - forceQuery, - ) - - override fun deepCopy(): UrlBuilder { - val builder = this - return UrlBuilder().apply { - scheme = builder.scheme - host = builder.host - port = builder.port - path = builder.path - parameters = builder.parameters.deepCopy() - fragment = builder.fragment - userInfo = builder.userInfo?.copy() - forceQuery = builder.forceQuery - } - } - - override fun toString(): String = - "UrlBuilder(scheme=$scheme, host='$host', port=$port, path='$path', parameters=$parameters, fragment=$fragment, userInfo=$userInfo, forceQuery=$forceQuery)" -} - -public fun UrlBuilder.parameters(block: QueryParametersBuilder.() -> Unit) { - parameters.apply(block) -} - -@InternalApi -public val UrlBuilder.encodedPath: String - get() = encodePath(path, parameters.entries(), fragment, forceQuery) - -/** - * Constructs a [UserInfo] from its %-encoded representation. - */ -@InternalApi -public fun UserInfo(value: String): UserInfo { - val info = value.split(":") - return UserInfo( - info[0].urlDecodeComponent(), - if (info.size > 1) info[1].urlDecodeComponent() else "", - ) -} -*/ diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt deleted file mode 100644 index 3b06ecc97..000000000 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/UrlParser.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net - -/* -import aws.smithy.kotlin.runtime.text.splitAsQueryString -import aws.smithy.kotlin.runtime.text.urlDecodeComponent - -internal fun urlParseImpl(url: String, decoding: UrlDecoding): Url = - UrlBuilder { - var next = url - .captureUntilAndSkip("://") { - scheme = Scheme.parse(it) - } - .captureUntilAndSkip("@") { - userInfo = UserInfo(it) - } - - next = next.capture(0 until next.firstIndexOrEnd("/", "?", "#")) { - val (h, p) = it.splitHostPort() - host = h - port = p ?: scheme.defaultPort - } - - val pathDecode = decoding.applicatorFor(UrlDecoding.DecodePath) - if (next.startsWith("/")) { - next = next.capture(1 until next.firstIndexOrEnd("?", "#")) { - path = "/${pathDecode(it)}" - } - } - - if (next.startsWith("?")) { - val queryDecode = decoding.applicatorFor(UrlDecoding.DecodeQueryParameters) - next = next.capture(1 until next.firstIndexOrEnd("#")) { - it.splitAsQueryString().entries.forEach { (k, v) -> - parameters.appendAll(queryDecode(k), v.map(queryDecode)) - } - } - } - - if (next.startsWith('#') && next.length > 1) { - val fragmentDecode = decoding.applicatorFor(UrlDecoding.DecodeFragment) - fragment = fragmentDecode(next.substring(1)) - } - } - -private fun UrlDecoding.applicatorFor(part: UrlDecoding): (String) -> String = when (part) { - in this -> String::urlDecodeComponent - else -> { s -> s } -} - -private fun String.firstIndexOrEnd(vararg substring: String): Int { - val indices = substring - .map { indexOf(it) } - .filter { it != -1 } - if (indices.isEmpty()) return length - - return minOf(indices.min(), length) -} - -private fun String.captureUntilAndSkip(substring: String, block: (String) -> Unit): String { - val substringIndex = indexOf(substring) - if (substringIndex == -1) { - return this - } - - val slice = substring(0 until substringIndex) - block(slice) - return substring(substringIndex + substring.length) -} - -private fun String.capture(range: IntRange, block: (String) -> Unit): String { - val slice = substring(range) - if (slice.isNotEmpty()) { - block(slice) - } - return substring(range.last + 1) -} - -/** - * Parses a `host[:port]` pair. IPv6 hostnames MUST be enclosed in square brackets (`[]`). - */ -internal fun String.splitHostPort(): Pair { - val lBracketIndex = indexOf('[') - val rBracketIndex = indexOf(']') - val lastColonIndex = lastIndexOf(":") - val hostEndIndex = when { - rBracketIndex != -1 -> rBracketIndex + 1 - lastColonIndex != -1 -> lastColonIndex - else -> length - } - - require(lBracketIndex == -1 && rBracketIndex == -1 || lBracketIndex < rBracketIndex) { "unmatched [ or ]" } - require(lBracketIndex <= 0) { "unexpected characters before [" } - require(rBracketIndex == -1 || rBracketIndex == hostEndIndex - 1) { "unexpected characters after ]" } - - val host = if (lBracketIndex != -1) { - substring(lBracketIndex + 1 until rBracketIndex) - } else { - substring(0 until hostEndIndex) - } - - val decodedHost = host.urlDecodeComponent() - if (lBracketIndex != -1 && rBracketIndex != -1 && !decodedHost.isIpv6()) { - throw IllegalArgumentException("non-ipv6 host was enclosed in []-brackets") - } - - return Pair( - Host.parse(decodedHost), - if (hostEndIndex != -1 && hostEndIndex != length) substring(hostEndIndex + 1).toInt() else null, - ) -} -*/ diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt index 5321bd754..e1902bef9 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt @@ -4,15 +4,11 @@ */ package aws.smithy.kotlin.runtime.net.url -import aws.smithy.kotlin.runtime.net.Host -import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.net.isIpv6 -import aws.smithy.kotlin.runtime.net.toUrlString +import aws.smithy.kotlin.runtime.net.* import aws.smithy.kotlin.runtime.text.Scanner import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import aws.smithy.kotlin.runtime.text.ensurePrefix -import aws.smithy.kotlin.runtime.text.urlDecodeComponent import aws.smithy.kotlin.runtime.util.CanDeepCopy /** @@ -58,7 +54,7 @@ public class Url private constructor( } scanner.upToOrEnd("/", "?", "#") { authority -> - val (h, p) = authority.splitHostPort() + val (h, p) = authority.parseHostPort() host = h p?.let { port = it } } @@ -316,33 +312,30 @@ public class Url private constructor( } } -private fun String.splitHostPort(): Pair { - val lBracketIndex = indexOf('[') - val rBracketIndex = indexOf(']') - val lastColonIndex = lastIndexOf(":") - val hostEndIndex = when { - rBracketIndex != -1 -> rBracketIndex + 1 - lastColonIndex != -1 -> lastColonIndex - else -> length - } +private fun String.parseHostPort(): Pair = + if (startsWith('[')) { + val bracketEnd = indexOf(']') + require(bracketEnd > 0) { "unmatched [ or ]" } + + val encodedHostName = substring(1, bracketEnd) + val decodedHostName = PercentEncoding.Host.decode(encodedHostName) + val host = Host.parse(decodedHostName) + require(host is Host.IpAddress && host.address is IpV6Addr) { "non-ipv6 host was enclosed in []-brackets" } - require(lBracketIndex == -1 && rBracketIndex == -1 || lBracketIndex < rBracketIndex) { "unmatched [ or ]" } - require(lBracketIndex <= 0) { "unexpected characters before [" } - require(rBracketIndex == -1 || rBracketIndex == hostEndIndex - 1) { "unexpected characters after ]" } + val port = when (getOrNull(bracketEnd + 1)) { + ':' -> substring(bracketEnd + 2).toInt() + null -> null + else -> throw IllegalArgumentException("unexpected characters after ]") + } - val host = if (lBracketIndex != -1) { - substring(lBracketIndex + 1 until rBracketIndex) + host to port } else { - substring(0 until hostEndIndex) - } + val parts = split(':') - val decodedHost = host.urlDecodeComponent() - if (lBracketIndex != -1 && rBracketIndex != -1 && !decodedHost.isIpv6()) { - throw IllegalArgumentException("non-ipv6 host was enclosed in []-brackets") - } + val decodedHostName = PercentEncoding.Host.decode(parts[0]) + val host = Host.parse(decodedHostName) + require(host !is Host.IpAddress || host.address !is IpV6Addr) { "ipv6 host given without []-brackets" } - return Pair( - Host.parse(decodedHost), - if (hostEndIndex != -1 && hostEndIndex != length) substring(hostEndIndex + 1).toInt() else null, - ) -} + val port = parts.getOrNull(1)?.toInt() + host to port + } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt index adcdb76c4..4da637177 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/Text.kt @@ -7,220 +7,6 @@ package aws.smithy.kotlin.runtime.text import aws.smithy.kotlin.runtime.InternalApi -/** - * URL encode a string component according to - * https://tools.ietf.org/html/rfc3986#section-2 - */ -@InternalApi -public fun String.urlEncodeComponent( - formUrlEncode: Boolean = false, -): String { - val sb = StringBuilder(this.length) - val data = this.encodeToByteArray() - for (cbyte in data) { - val chr = cbyte.toInt().toChar() - when (chr) { - ' ' -> if (formUrlEncode) sb.append("+") else sb.append("%20") - // $2.3 Unreserved characters - in 'a'..'z', in 'A'..'Z', in '0'..'9', '-', '_', '.', '~' -> sb.append(chr) - else -> cbyte.percentEncodeTo(sb) - } - } - - return sb.toString() -} - -/** - * The set of reserved delimiters from section-2.2 allowed as a path character that do not require - * percent encoding. - * See 'pchar': https://tools.ietf.org/html/rfc3986#section-3.3 - */ -@InternalApi -public val VALID_PCHAR_DELIMS: Set = setOf( - '/', - ':', '@', - // sub-delims from section-2.2 - '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', - // unreserved section-2.3 - '-', '.', '_', '~', -) - -// test if a string encoded to a ByteArray is already percent encoded starting at index [i] -private fun isPercentEncodedAt(d: ByteArray, i: Int): Boolean = - i + 2 < d.size && - d[i].toInt().toChar() == '%' && - i + 2 < d.size && - d[i + 1].toInt().toChar().uppercaseChar() in upperHexSet && - d[i + 2].toInt().toChar().uppercaseChar() in upperHexSet - -/** - * Encode a string that represents a *raw* URL path according to - * https://tools.ietf.org/html/rfc3986#section-3.3 - */ -@InternalApi -public fun String.encodeUrlPath(): String = encodeUrlPath(VALID_PCHAR_DELIMS, checkPercentEncoded = true) - -/** - * Encode a string that represents a raw URL path component. Everything EXCEPT alphanumeric characters - * and all delimiters in the [validDelimiters] set will be percent encoded. - * - * @param validDelimiters the set of allowed delimiters that need not be percent-encoded - * @param checkPercentEncoded flag indicating if the encoding process should check for already percent-encoded - * characters and pass them through as is or not. - */ -@InternalApi -public fun String.encodeUrlPath(validDelimiters: Set, checkPercentEncoded: Boolean): String { - val sb = StringBuilder(this.length) - val data = this.encodeToByteArray() - - var i = 0 - while (i < data.size) { - // 3.3 pchar: pct-encoded - if (checkPercentEncoded && isPercentEncodedAt(data, i)) { - sb.append(data[i++].toInt().toChar()) - sb.append(data[i++].toInt().toChar()) - sb.append(data[i++].toInt().toChar()) - continue - } - - val cbyte = data[i] - when (val chr = cbyte.toInt().toChar()) { - // unreserved - in 'a'..'z', in 'A'..'Z', in '0'..'9', in validDelimiters -> sb.append(chr) - else -> cbyte.percentEncodeTo(sb) - } - i++ - } - - return sb.toString() -} - -/** - * Normalizes the segments of a URL path according to the following rules: - * * The returned path always begins with `/` (e.g., `a/b/c` → `/a/b/c`) - * * The returned path ends with `/` if the input path also does - * * Empty segments are discarded (e.g., `/a//b` → `/a/b`) - * * Segments of `.` are discarded (e.g., `/a/./b` → `/a/b`) - * * Segments of `..` are used to discard ancestor paths (e.g., `/a/b/../c` → `/a/c`) - * * All other segments are unmodified - */ -@InternalApi -public fun String.normalizePathSegments(segmentTransform: ((String) -> String)?): String { - val segments = split("/").filter(String::isNotEmpty) - var skip = 0 - val normalizedSegments = buildList { - segments.asReversed().forEach { - when { - it == "." -> { } // Ignore - it == ".." -> skip++ - skip > 0 -> skip-- - else -> add(it) - } - } - }.asReversed() - require(skip == 0) { "Found too many `..` instances for path segment count" } - - return normalizedSegments.joinToString( - separator = "/", - prefix = "/", - postfix = if (normalizedSegments.isNotEmpty() && endsWith("/")) "/" else "", - transform = segmentTransform, - ) -} - -@InternalApi -public fun String.transformPathSegments(segmentTransform: ((String) -> String)?): String = - split("/").joinToString(separator = "/", transform = segmentTransform) - -private const val upperHex: String = "0123456789ABCDEF" -private val upperHexSet = upperHex.toSet() - -// $2.1 Percent-Encoding -@InternalApi -public fun Byte.percentEncodeTo(out: Appendable) { - val code = toInt() and 0xff - out.append('%') - out.append(upperHex[code shr 4]) - out.append(upperHex[code and 0x0f]) -} - -/** - * Split a (decoded) query string "foo=baz&bar=quux" into it's component parts - */ -@InternalApi -public fun String.splitAsQueryString(): Map> { - val entries = mutableMapOf>() - split("&") - .forEach { pair -> - val parts = pair.split("=", limit = 2) - val key = parts[0] - val value = when (parts.size) { - 1 -> "" - 2 -> parts[1] - else -> throw IllegalArgumentException("invalid query string: $parts") - } - if (entries.containsKey(key)) { - entries[key]!!.add(value) - } else { - entries[key] = mutableListOf(value) - } - } - return entries -} - -/** - * Decode a URL's query string, resolving percent-encoding (e.g., "%3B" → ";"). - */ -@InternalApi -public fun String.urlDecodeComponent(formUrlDecode: Boolean = false): String { - val orig = this - return buildString(orig.length) { - var byteBuffer: ByteArray? = null // Do not initialize unless needed - var i = 0 - var c: Char - while (i < orig.length) { - c = orig[i] - when { - c == '+' && formUrlDecode -> { - append(' ') - i++ - } - - c == '%' -> { - if (byteBuffer == null) { - byteBuffer = ByteArray((orig.length - i) / 3) // Max remaining percent-encoded bytes - } - - var byteCount = 0 - while ((i + 2) < orig.length && c == '%') { - val byte = orig.substring(i + 1, i + 3).toIntOrNull(radix = 16)?.toByte() ?: break - byteBuffer[byteCount++] = byte - - i += 3 - if (i < orig.length) c = orig[i] - } - - append(byteBuffer.decodeToString(endIndex = byteCount)) - - if (i != orig.length && c == '%') { - append(c) - i++ - } - } - - else -> { - append(c) - i++ - } - } - } - } -} - -@InternalApi -public fun String.urlReencodeComponent(formUrlDecode: Boolean = false, formUrlEncode: Boolean = false): String = - urlDecodeComponent(formUrlDecode).urlEncodeComponent(formUrlEncode) - @InternalApi public fun String.ensurePrefix(prefix: String): String = if (startsWith(prefix)) this else prefix + this diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt index 2523d5faf..4986c89bf 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt @@ -28,6 +28,7 @@ public class PercentEncoding( private const val UPPER_HEX = "0123456789ABCDEF" + public val Host: Encoding = PercentEncoding("host", UNRESERVED + ':') // e.g., for IPv6 zone ID encoding public val UserInfo: Encoding = PercentEncoding("user info", VALID_UCHAR) public val Path: Encoding = PercentEncoding("path", VALID_PCHAR) public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR) diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/TextTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/TextTest.kt deleted file mode 100644 index 83297f902..000000000 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/text/TextTest.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package aws.smithy.kotlin.runtime.text - -import io.kotest.matchers.maps.shouldContain -import kotlin.test.* - -data class EscapeTest(val input: String, val expected: String, val formUrlEncode: Boolean = false) - -class TextTest { - @Test - fun urlValuesEncodeCorrectly() { - val nonEncodedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" - val encodedCharactersInput = "\t\n\r !\"#$%&'()*+,/:;<=>?@[\\]^`{|}" - val encodedCharactersOutput = - "%09%0A%0D%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D" - - val tests: List = listOf( - EscapeTest("", ""), - EscapeTest("abc", "abc"), - EscapeTest("a b", "a%20b"), - EscapeTest("a b", "a+b", formUrlEncode = true), - EscapeTest("10%", "10%25"), - EscapeTest(nonEncodedCharacters, nonEncodedCharacters), - EscapeTest(encodedCharactersInput, encodedCharactersOutput), - ) - - for (test in tests) { - val actual = test.input.urlEncodeComponent(test.formUrlEncode) - assertEquals(test.expected, actual, "expected ${test.expected}; got: $actual") - } - } - - @Test - fun formDataValuesEncodeCorrectly() { - val nonEncodedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_." - val encodedCharactersInput = "\t\n\r !\"#$%&'()+,/:;<=>?@[\\]^`{|}" - val encodedCharactersOutput = - "%09%0A%0D+%21%22%23%24%25%26%27%28%29%2B%2C%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D" - - val tests: List = listOf( - EscapeTest("", ""), - EscapeTest("a b", "a+b"), - EscapeTest(nonEncodedCharacters, nonEncodedCharacters), - EscapeTest(encodedCharactersInput, encodedCharactersOutput), - ) - - for (test in tests) { - val actual = test.input.urlEncodeComponent(true) - assertEquals(test.expected, actual, "expected ${test.expected}; got: $actual") - } - } - - @Test - fun urlPathValuesEncodeCorrectly() { - val urlPath = "/wikipedia/en/6/61/Purdue_University_\u2013seal.svg" - assertEquals("/wikipedia/en/6/61/Purdue_University_%E2%80%93seal.svg", urlPath.encodeUrlPath()) - assertEquals("/kotlin/Tue,%2029%20Apr%202014%2018:30:38%20GMT", "/kotlin/Tue, 29 Apr 2014 18:30:38 GMT".encodeUrlPath()) - } - - @Test - fun respectsAlreadyEncodedUrls() { - val urlPath = "/wikipedia/en/6/61/Purdue_University_%E2%80%93seal.svg" - assertEquals("/wikipedia/en/6/61/Purdue_University_%E2%80%93seal.svg", urlPath.encodeUrlPath()) - } - - @Test - fun testNormalizePathSegments() { - fun assertNormalize(unnormalized: String, expected: String) { - val actual = unnormalized.normalizePathSegments(String::uppercase) - assertEquals(expected, actual, "Unexpected normalization for `$unnormalized`") - } - - val tests = mapOf( - "" to "/", - "/" to "/", - "foo" to "/FOO", - "/foo" to "/FOO", - "foo/" to "/FOO/", - "/foo/" to "/FOO/", - "/a/b/c" to "/A/B/C", - "/a/b/../c" to "/A/C", - "/a/./c" to "/A/C", - "/./" to "/", - "/a/b/./../c" to "/A/C", - "/a/b/c/d/../e/../../f/../../../g" to "/G", - "//a//b//c//" to "/A/B/C/", - ) - tests.forEach { (unnormalized, expected) -> assertNormalize(unnormalized, expected) } - } - - @Test - fun testNormalizePathSegmentsError() { - assertFailsWith(IllegalArgumentException::class) { - "/a/b/../../..".normalizePathSegments { it } - } - } - - @Test - fun utf8UrlPathValuesEncodeCorrectly() { - val swissAndGerman = "\u0047\u0072\u00fc\u0065\u007a\u0069\u005f\u007a\u00e4\u006d\u00e4" - val russian = "\u0412\u0441\u0435\u043c\u005f\u043f\u0440\u0438\u0432\u0435\u0442" - val japanese = "\u3053\u3093\u306b\u3061\u306f" - assertEquals("%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82", russian.encodeUrlPath()) - assertEquals("Gr%C3%BCezi_z%C3%A4m%C3%A4", swissAndGerman.encodeUrlPath()) - assertEquals("%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF", japanese.encodeUrlPath()) - } - - @Test - fun splitQueryStringIntoParts() { - val query = "foo=baz&bar=quux&foo=qux&a=" - val actual = query.splitAsQueryString() - val expected = mapOf( - "foo" to listOf("baz", "qux"), - "bar" to listOf("quux"), - "a" to listOf(""), - ) - - expected.entries.forEach { entry -> - actual.shouldContain(entry.key, entry.value) - } - - val queryNoEquals = "abc=cde&noequalssign" - val actualNoEquals = queryNoEquals.splitAsQueryString() - val expectedNoEquals = mapOf( - "abc" to listOf("cde"), - "noequalssign" to listOf(""), - ) - expectedNoEquals.entries.forEach { entry -> - actualNoEquals.shouldContain(entry.key, entry.value) - } - - val queryValueWithEquals = "foo=bar&baz=quux==" - val actualValueWithEquals = queryValueWithEquals.splitAsQueryString() - val expectedValueWithEquals = mapOf( - "foo" to listOf("bar"), - "baz" to listOf("quux=="), - ) - expectedValueWithEquals.entries.forEach { entry -> - actualValueWithEquals.shouldContain(entry.key, entry.value) - } - } - - @Test - fun decodeUrlComponent() { - val component = "a%3Bb+c%7Ed%20e%2Bf+g%3D%E1%88%B4" - val expected = "a;b+c~d e+f+g=ሴ" - assertEquals(expected, component.urlDecodeComponent()) - } - - @Test - fun decodeUrlComponentWithFormUrl() { - val component = "a%3Bb+c%7Ed%20e%2Bf+g%3D%E1%88%B4" - val expected = "a;b c~d e+f g=ሴ" - assertEquals(expected, component.urlDecodeComponent(true)) - } - - @Test - fun decodeUrlComponentInvalidSequence() { - val component = "%20%&'%%%f" - val expected = " %&'%%%f" // Only the %20 was valid - assertEquals(expected, component.urlDecodeComponent()) - } - - @Test - fun reencodeUrlComponent() { - val component = "ሴ%E1%88%B4" - val expected = "%E1%88%B4%E1%88%B4" - assertEquals(expected, component.urlReencodeComponent()) - } -} diff --git a/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt b/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt index 19f04fc67..6983a956d 100644 --- a/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt +++ b/runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt @@ -11,7 +11,7 @@ import aws.smithy.kotlin.runtime.content.BigInteger import aws.smithy.kotlin.runtime.content.Document import aws.smithy.kotlin.runtime.io.SdkBuffer import aws.smithy.kotlin.runtime.serde.* -import aws.smithy.kotlin.runtime.text.urlEncodeComponent +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.TimestampFormat import kotlin.contracts.ExperimentalContracts @@ -40,7 +40,7 @@ private class FormUrlSerializer( buffer.apply(block) } - private fun write(value: String) = write { writeUtf8(value.urlEncodeComponent()) } + private fun write(value: String) = write { writeUtf8(value.encode()) } override fun serializeBoolean(value: Boolean) = write("$value") override fun serializeByte(value: Byte) = write { commonWriteNumber(value) } @@ -233,7 +233,7 @@ private class FormUrlListSerializer( override fun serializeDouble(value: Double) = writePrefixed { commonWriteNumber(value) } override fun serializeBigInteger(value: BigInteger) = writePrefixed { commonWriteNumber(value) } override fun serializeBigDecimal(value: BigDecimal) = writePrefixed { writeUtf8(value.toPlainString()) } - override fun serializeString(value: String) = writePrefixed { writeUtf8(value.urlEncodeComponent()) } + override fun serializeString(value: String) = writePrefixed { writeUtf8(value.encode()) } override fun serializeInstant(value: Instant, format: TimestampFormat) = writePrefixed { writeUtf8(value.format(format)) } override fun serializeSdkSerializable(value: SdkSerializable) { @@ -265,9 +265,7 @@ private class FormUrlMapSerializer( private fun writeKey(key: String) { idx++ if (buffer.size > 0L) buffer.writeUtf8("&") - - val encodedKey = key.urlEncodeComponent() - buffer.writeUtf8("$commonPrefix.${mapName.key}=$encodedKey") + buffer.writeUtf8("$commonPrefix.${mapName.key}=${key.encode()}") } private fun writeEntry(key: String, block: () -> Unit) { @@ -378,3 +376,5 @@ private fun SdkFieldDescriptor.copyWithNewSerialName(newName: String): SdkFieldD newTraits.add(FormUrlSerialName(newName)) return SdkFieldDescriptor(kind, newTraits) } + +private fun String.encode() = PercentEncoding.Query.encode(this) diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt index 16fbeead4..4e200e464 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt @@ -7,9 +7,9 @@ package aws.smithy.kotlin.runtime.client.endpoints.functions import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.net.* +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import aws.smithy.kotlin.runtime.net.url.Url as SdkUrl import aws.smithy.kotlin.runtime.text.ensureSuffix -import aws.smithy.kotlin.runtime.text.urlEncodeComponent @InternalApi public fun substring(value: String?, start: Int, stop: Int, reverse: Boolean): String? = @@ -28,7 +28,7 @@ public fun isValidHostLabel(value: String?, allowSubdomains: Boolean): Boolean = } ?: false @InternalApi -public fun uriEncode(value: String): String = value.urlEncodeComponent(formUrlEncode = false) +public fun uriEncode(value: String): String = PercentEncoding.Query.encode(value) @InternalApi public fun parseUrl(value: String?): Url? = From ea812d005e97309adca51ddf199c995ae4c5f8f7 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Fri, 17 Nov 2023 05:24:35 +0000 Subject: [PATCH 07/17] Lint --- .../protocol/HttpBindingProtocolGenerator.kt | 2 +- .../src/aws/smithy/kotlin/runtime/crt/Http.kt | 3 - .../http/engine/crt/ConnectionManager.kt | 2 +- .../kotlin/runtime/collections/MultiMap.kt | 6 +- .../runtime/collections/MutableMultiMap.kt | 6 +- .../collections/views/CollectionView.kt | 2 +- .../smithy/kotlin/runtime/net/url/UrlPath.kt | 2 - .../kotlin/runtime/net/QueryParametersTest.kt | 154 -------------- .../aws/smithy/kotlin/runtime/net/UrlTest.kt | 196 ------------------ .../kotlin/runtime/net/url/UrlParsingTest.kt | 1 - .../smithy/kotlin/runtime/net/url/UrlTest.kt | 118 ----------- .../client/endpoints/functions/Functions.kt | 6 +- .../smithy/test/HttpRequestTestBuilderTest.kt | 2 +- 13 files changed, 14 insertions(+), 486 deletions(-) delete mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt delete mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt delete mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index ff03f9f87..6b985f2e2 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -326,7 +326,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { val tsLabel = formatInstant( "input.${binding.member.defaultName()}$nullCheck", tsFormat, - forceString = true + forceString = true, ) tsLabel } else { diff --git a/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt b/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt index 6c627d12b..9e2d73f23 100644 --- a/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt +++ b/runtime/crt-util/jvm/src/aws/smithy/kotlin/runtime/crt/Http.kt @@ -11,9 +11,6 @@ import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.net.url.QueryParameters -import aws.smithy.kotlin.runtime.net.url.Url -import aws.smithy.kotlin.runtime.text.ensurePrefix -import aws.smithy.kotlin.runtime.text.ensureSuffix import kotlin.coroutines.coroutineContext import aws.sdk.kotlin.crt.http.Headers as HeadersCrt import aws.sdk.kotlin.crt.http.HttpRequest as HttpRequestCrt diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt index 2b12dbf4f..03d94ee6f 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt @@ -97,7 +97,7 @@ internal class ConnectionManager( authType = when { proxyConfig.url.userInfo.isNotEmpty -> HttpProxyAuthorizationType.Basic else -> HttpProxyAuthorizationType.None - } + }, ) else -> null } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt index 69dd89b32..1f2145657 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt @@ -16,9 +16,9 @@ public interface MultiMap : Map> { public fun multiMapOf(vararg pairs: Pair): MultiMap = SimpleMultiMap(pairs.groupBy(Pair::first, Pair::second)) -internal class SimpleMultiMap(private val delegate: Map>) : - MultiMap, Map> by delegate -{ +internal class SimpleMultiMap( + private val delegate: Map>, +) : MultiMap, Map> by delegate { override val entryValues: Sequence> get() = sequence { entries.forEach { (key, values) -> diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt index 743faab3b..f35191ad0 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt @@ -35,9 +35,9 @@ public interface MutableMultiMap : MutableMap> { public fun mutableMultiMapOf(vararg pairs: Pair): MutableMultiMap = SimpleMutableMultiMap(pairs.groupByTo(mutableMapOf(), Pair::first, Pair::second)) -internal class SimpleMutableMultiMap(private val delegate: MutableMap>) : - MutableMap> by delegate, MutableMultiMap -{ +internal class SimpleMutableMultiMap( + private val delegate: MutableMap>, +) : MutableMap> by delegate, MutableMultiMap { private fun ensureKey(key: K) = getOrPut(key, ::mutableListOf) override fun add(key: K, value: V): Boolean = ensureKey(key).add(value) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt index 34ab3845c..659d36dde 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/CollectionView.kt @@ -8,7 +8,7 @@ internal open class CollectionView( private val src: Collection, private val src2Dest: (Src) -> Dest, private val dest2Src: (Dest) -> Src, -): Collection, IterableView(src, src2Dest) { +) : Collection, IterableView(src, src2Dest) { override fun contains(element: Dest): Boolean = src.contains(dest2Src(element)) override fun containsAll(elements: Collection): Boolean = src.containsAll(elements.asView(dest2Src, src2Dest)) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt index 3d0d798cd..7a30e422e 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt @@ -4,11 +4,9 @@ */ package aws.smithy.kotlin.runtime.net.url -import aws.smithy.kotlin.runtime.collections.views.MutableListView import aws.smithy.kotlin.runtime.collections.views.asView import aws.smithy.kotlin.runtime.text.encoding.Encodable import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding -import aws.smithy.kotlin.runtime.util.CanDeepCopy /** * Represents the path component of a URL diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt deleted file mode 100644 index 105ec571e..000000000 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/QueryParametersTest.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net - -/* -import kotlin.test.* - -class QueryParametersTest { - - @Test - fun itBuilds() { - val params = QueryParameters { - append("foo", "baz") - appendAll("foo", listOf("bar", "quux")) - append("qux", "john") - remove("qux") - } - assertEquals(params.getAll("foo"), listOf("baz", "bar", "quux")) - assertTrue(params.contains("foo", "quux")) - assertFalse(params.contains("qux")) - params.forEach { name, values -> - when (name) { - "foo" -> assertEquals(values, listOf("baz", "bar", "quux")) - } - } - } - - @Test - fun itEncodesToQueryString() { - data class QueryParamTest(val params: QueryParameters, val expected: String) - val tests: List = listOf( - QueryParamTest( - QueryParameters { - append("q", "puppies") - append("oe", "utf8") - }, - "oe=utf8&q=puppies", - ), - QueryParamTest( - QueryParameters { - appendAll("q", listOf("dogs", "&", "7")) - }, - "q=dogs&q=%26&q=7", - ), - QueryParamTest( - QueryParameters { - appendAll("a", listOf("a1", "a2", "a3")) - appendAll("b", listOf("b1", "b2", "b3")) - appendAll("c", listOf("c1", "c2", "c3")) - }, - "a=a1&a=a2&a=a3&b=b1&b=b2&b=b3&c=c1&c=c2&c=c3", - ), - ) - for (test in tests) { - val actual = test.params.urlEncode() - assertEquals(test.expected, actual, "expected ${test.expected}; got: $actual") - } - } - - @Test - fun testSubsequentModificationsDontAffectOriginal() { - val builder = QueryParametersBuilder() - - builder.append("a", "alligator") - builder.append("b", "bunny") - builder.append("c", "chinchilla") - val first = builder.build() - val firstExpected = mapOf( - "a" to listOf("alligator"), - "b" to listOf("bunny"), - "c" to listOf("chinchilla"), - ) - - builder.append("a", "anteater") - builder.remove("b") - builder["c"] = "crocodile" - val second = builder.build() - val secondExpected = mapOf( - "a" to listOf("alligator", "anteater"), - "c" to listOf("crocodile"), - ) - - assertEquals(firstExpected.entries, first.entries()) - assertEquals(secondExpected.entries, second.entries()) - } - - @Test - fun splitQueryStringIntoParts() { - val query = "foo=baz&bar=quux&foo=qux&a=" - val actual = query.splitAsQueryParameters() - val expected = QueryParameters { - append("foo", "baz") - append("foo", "qux") - append("bar", "quux") - append("a", "") - } - - expected.entries().forEach { entry -> - entry.value.forEach { value -> - assertTrue(actual.contains(entry.key, value), "parsed query does not contain ${entry.key}:$value") - } - } - - val queryNoEquals = "abc=cde&noequalssign" - val actualNoEquals = queryNoEquals.splitAsQueryParameters() - val expectedNoEquals = QueryParameters { - append("abc", "cde") - append("noequalssign", "") - } - expectedNoEquals.entries().forEach { entry -> - entry.value.forEach { value -> - assertTrue(actualNoEquals.contains(entry.key, value), "parsed query does not contain ${entry.key}:$value") - } - } - } - - @Test - fun testFullUriToQueryParameters() { - val uri = "http://foo.com?a=apple&b=banana&c=cherry" - val params = uri.fullUriToQueryParameters() - assertNotNull(params) - assertEquals("apple", params["a"]) - assertEquals("banana", params["b"]) - assertEquals("cherry", params["c"]) - } - - @Test - fun testFullUriToQueryParameters_withFragment() { - val uri = "http://foo.com?a=apple&b=banana&c=cherry#d=durian" - val params = uri.fullUriToQueryParameters() - assertNotNull(params) - assertEquals("apple", params["a"]) - assertEquals("banana", params["b"]) - assertEquals("cherry", params["c"]) - assertFalse("d" in params) - } - - @Test - fun testFullUriToQueryParameters_emptyQueryString() { - val uri = "http://foo.com?" - val params = uri.fullUriToQueryParameters() - assertNull(params) - } - - @Test - fun testFullUriToQueryParameters_noQueryString() { - val uri = "http://foo.com" - val params = uri.fullUriToQueryParameters() - assertNull(params) - } -} -*/ \ No newline at end of file diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt deleted file mode 100644 index dd9816418..000000000 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/UrlTest.kt +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net - -/* -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFails - -class UrlTest { - @Test - fun basicToString() { - val expected = "https://test.aws.com/kotlin" - val url = Url( - Scheme.HTTPS, - Host.Domain("test.aws.com"), - path = "/kotlin", - ) - assertEquals(expected, url.toString()) - } - - @Test - fun forceRetainQuery() { - val expected = "https://test.aws.com/kotlin?" - val url = UrlBuilder { - host = Host.Domain("test.aws.com") - path = "/kotlin" - forceQuery = true - } - assertEquals(expected, url.toString()) - } - - @Test - fun withParameters() { - val expected = "https://test.aws.com/kotlin?baz=quux&baz=qux&foo=bar" - val params = QueryParameters { - append("foo", "bar") - appendAll("baz", listOf("quux", "qux")) - } - - val url = Url( - Scheme.HTTPS, - Host.Domain("test.aws.com"), - path = "/kotlin", - parameters = params, - ) - assertEquals(expected, url.toString()) - } - - @Test - fun specificPort() { - val expected = "https://test.aws.com:8000" - val url = Url( - Scheme.HTTPS, - Host.Domain("test.aws.com"), - port = 8000, - ) - assertEquals(expected, url.toString()) - - val expected2 = "http://test.aws.com" - val url2 = Url( - Scheme.HTTP, - Host.Domain("test.aws.com"), - port = 80, - ) - assertEquals(expected2, url2.toString()) - } - - @Test - fun portRange() { - fun checkPort(n: Int) { - assertEquals( - n, - Url( - Scheme.HTTPS, - Host.Domain("test.aws.com"), - port = n, - ).port, - ) - } - - checkPort(1) - checkPort(65535) - assertFails { - checkPort(65536) - } - } - - @Test - fun userinfoNoPassword() { - val expected = "https://user@test.aws.com" - val url = UrlBuilder { - scheme = Scheme.HTTPS - host = Host.Domain("test.aws.com") - userInfo = UserInfo("user", "") - } - assertEquals(expected, url.toString()) - } - - @Test - fun fullUserinfo() { - val expected = "https://user:password@test.aws.com" - val url = UrlBuilder { - scheme = Scheme.HTTPS - host = Host.Domain("test.aws.com") - userInfo = UserInfo("user", "password") - } - assertEquals(expected, url.toString()) - } - - @Test - fun itBuilds() { - val builder = UrlBuilder() - builder.scheme = Scheme.HTTP - builder.host = Host.Domain("test.aws.com") - builder.path = "/kotlin" - val url = builder.build() - val expected = "http://test.aws.com/kotlin" - assertEquals(expected, url.toString()) - assertEquals(Scheme.HTTP, builder.scheme) - assertEquals(Host.Domain("test.aws.com"), builder.host) - assertEquals(null, builder.port) - assertEquals(null, builder.fragment) - assertEquals(null, builder.userInfo) - } - - @Test - fun itBuildsWithNonDefaultPort() { - val url = UrlBuilder { - scheme = Scheme.HTTP - host = Host.Domain("test.aws.com") - path = "/kotlin" - port = 3000 - } - val expected = "http://test.aws.com:3000/kotlin" - assertEquals(expected, url.toString()) - } - - @Test - fun itBuildsWithParameters() { - val url = UrlBuilder { - scheme = Scheme.HTTP - host = Host.Domain("test.aws.com") - path = "/kotlin" - parameters { - append("foo", "baz") - } - } - val expected = "http://test.aws.com/kotlin?foo=baz" - assertEquals(expected, url.toString()) - } - - fun testEncodePath() { - val url = UrlBuilder() - url.parameters { - appendAll("q", listOf("dogs", "&", "7")) - append("empty", "") - } - url.path = "/foo/bar" - url.fragment = "header1" - val expected = "/foo/bar?empty=&q=dogs&q=%26&q=7#header1" - assertEquals(expected, url.encodedPath) - assertEquals(expected, url.build().encodedPath) - - val noParams = UrlBuilder { - path = "/foo/bar" - } - assertEquals("/foo/bar", noParams.encodedPath) - } - - @Test - fun testDeepCopy() { - val builder1 = UrlBuilder().apply { - host = Host.Domain("foo.com") - port = 1234 - parameters { - append("a", "alligator") - append("b", "bear") - } - } - - val builder2 = builder1.deepCopy() - - builder1.host = Host.Domain("bar.org") - builder1.port = 4321 - builder1.parameters.append("c", "chinchilla") - - val url1 = builder1.build().toString() - assertEquals("https://bar.org:4321?a=alligator&b=bear&c=chinchilla", url1) - val url2 = builder2.build().toString() - assertEquals("https://foo.com:1234?a=alligator&b=bear", url2) - } -} -*/ \ No newline at end of file diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt index 5b3f1b721..cc57fc2bd 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt @@ -6,7 +6,6 @@ package aws.smithy.kotlin.runtime.net.url import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme -import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import kotlin.test.* class UrlParsingTest { diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt deleted file mode 100644 index 76751dc68..000000000 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.smithy.kotlin.runtime.net.url - -/* -import kotlin.test.Test -import kotlin.test.fail -import aws.smithy.kotlin.runtime.net.Url as OldUrl -import aws.smithy.kotlin.runtime.net.url.Url as NewUrl - -class UrlTest { - private fun testEquivalence(vararg urls: String) { - val errors = urls.mapNotNull { url -> - val old = OldUrl.parse(url).toString() - val new = NewUrl.parse(url).toString() - if (old == new) null else "Old: <$old>, New: <$new>" - } - if (errors.isNotEmpty()) { - val message = errors.joinToString("\n", "Found the following errors:\n") { " $it" } - fail(message) - } - } - - @Test - fun testEquivalenceOfBasicUrls() { - testEquivalence( - "http://amazon.com", - "https://amazon.com", - ) - } - - @Test - fun testEquivalenceOfPorts() { - testEquivalence( - "https://amazon.com:8443", - ) - } - - @Test - fun testEquivalenceOfPaths() { - testEquivalence( - // "https://amazon.com/", // Old parser strips path `/`...is bug? - "https://amazon.com//", - "https://amazon.com/foo", - "https://amazon.com/foo/bar", - "https://amazon.com/foo/bar/", - "https://amazon.com/foo//bar/", - "https://amazon.com/foo/bar//", - ) - } - - @Test - fun testEquivalenceOfQueryStrings() { - testEquivalence( - // "https://amazon.com?", // Old parser strips '?' with empty query params...is bug? - "https://amazon.com?foo=bar", - // "https://amazon.com?foo=bar&baz=qux", // Old parser sorts query by key...is bug? - "https://amazon.com?foo=f%F0%9F%98%81o", - // "https://amazon.com?foo=f+o%20o", // Old parser encodes qparam ' ' to %20 instead of '+'...is bug? - ) - } - - @Test - fun testEquivalenceOfFragments() { - testEquivalence( - // "https://amazon.com#", // Old parser ignores empty fragments...is bug? - "https://amazon.com#foo", - "https://amazon.com#f%F0%9F%98%81o", - ) - } - - @Test - fun testEquivalenceOfMixedPathsAndQueryStrings() { - testEquivalence( - // "https://amazon.com/?", // Old parser strips '?' with empty query params...is bug? - // "https://amazon.com/?bar=baz", // Old parser strips path `/`...is bug? - // "https://amazon.com/foo?", // Old parser strips '?' with empty query params...is bug? - "https://amazon.com/foo?bar=baz", - ) - } - - @Test - fun testEquivalenceOfMixedPathsAndFragments() { - testEquivalence( - // "https://amazon.com/#", // Old parser strips path `/` and ignores empty fragments...are bugs? - // "https://amazon.com/#bar", // Old parser strips path `/`...is bug? - // "https://amazon.com/foo#", // Old parser ignores empty fragments...is bug? - "https://amazon.com/foo#bar", - ) - } - - @Test - fun testEquivalenceOfMixedQueryStringsAndFragments() { - testEquivalence( - // "https://amazon.com?#", // Old parser strips '?' with empty query params and ignores empty fragments...are bugs? - // "https://amazon.com?#baz", // Old parser strips '?' with empty query params...is bug? - // "https://amazon.com?foo=bar#", // Old parser ignores empty fragments...is bug? - "https://amazon.com?foo=bar#baz", - ) - } - - @Test - fun testEquivalenceOfMixedPathsAndQueryStringsAndFragments() { - testEquivalence( - // "https://amazon.com/?#", - // "https://amazon.com/?#quux", - // "https://amazon.com/?baz=qux#", - // "https://amazon.com/?baz=qux#quux", - // "https://amazon.com/foo/bar?#", - // "https://amazon.com/foo/bar?#quux", - // "https://amazon.com/foo/bar?baz=qux#", - "https://amazon.com/foo/bar?baz=qux#quux", - ) - } -} -*/ diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt index 4e200e464..b1bb85103 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/functions/Functions.kt @@ -6,10 +6,12 @@ package aws.smithy.kotlin.runtime.client.endpoints.functions import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.net.* +import aws.smithy.kotlin.runtime.net.Host +import aws.smithy.kotlin.runtime.net.isValidHostname +import aws.smithy.kotlin.runtime.net.toUrlString import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding -import aws.smithy.kotlin.runtime.net.url.Url as SdkUrl import aws.smithy.kotlin.runtime.text.ensureSuffix +import aws.smithy.kotlin.runtime.net.url.Url as SdkUrl @InternalApi public fun substring(value: String?, start: Int, stop: Int, reverse: Boolean): String? = diff --git a/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt b/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt index 9f02bfd78..a3a42e704 100644 --- a/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt +++ b/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt @@ -234,7 +234,7 @@ class HttpRequestTestBuilderTest { val request = HttpRequest { method = HttpMethod.POST - url{ + url { path.encoded = "/foo" parameters.decodedParameters { add("baz", "quux") From c047b0871aee4df7857cb8de603bc1d944f0f843 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Fri, 17 Nov 2023 18:09:59 +0000 Subject: [PATCH 08/17] Add form-url PercentEncoding --- .../software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt | 2 +- .../aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index cef9229cd..68c592eea 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -175,8 +175,8 @@ object RuntimeTypes { val Host = symbol("Host") object Url : RuntimeTypePackage(KotlinDependency.CORE, "net.url") { + val QueryParameters = symbol("QueryParameters") val Url = symbol("Url") - val UrlDecoding = symbol("UrlDecoding") } } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt index 4986c89bf..ba07ad12c 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt @@ -33,6 +33,7 @@ public class PercentEncoding( public val Path: Encoding = PercentEncoding("path", VALID_PCHAR) public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR) public val Fragment: Encoding = PercentEncoding("fragment", VALID_FCHAR) + public val FormUrl: Encoding = PercentEncoding("form URL", VALID_QCHAR, mapOf(' ' to '+')) private fun percentAsciiEncode(char: Char) = buildString { val value = char.code and 0xff From b25993c5adbedc9782d0415e2882b0a54d832f59 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Fri, 17 Nov 2023 22:58:27 +0000 Subject: [PATCH 09/17] Throw a higher-level exception when URL parsing fails; properly set trailingSlash in HTTP bindings --- .../protocol/HttpBindingProtocolGenerator.kt | 7 ++- .../aws/smithy/kotlin/runtime/net/url/Url.kt | 46 ++++++++++--------- .../runtime/client/endpoints/Endpoint.kt | 1 - 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index 71b2045ee..0e09f7c80 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -320,8 +320,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { // spec dictates member name and label name MUST be the same val binding = pathBindings.find { binding -> binding.memberName == segment.content - } - ?: throw CodegenException("failed to find corresponding member for httpLabel `${segment.content}") + } ?: throw CodegenException("failed to find corresponding member for httpLabel `${segment.content}") // shape must be string, number, boolean, or timestamp val targetShape = ctx.model.expectShape(binding.member.target) @@ -355,6 +354,10 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { } } } + + if (httpTrait.uri.segments.isEmpty()) { + writer.write("path.trailingSlash = true") + } } else { // all literals, inline directly val resolvedPath = httpTrait.uri.segments.joinToString( diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt index e1902bef9..87c07989b 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt @@ -45,35 +45,39 @@ public class Url private constructor( * [UrlEncoding.All], meaning that the entire URL string is properly encoded. * @return A new [Url] instance */ - public fun parse(value: String, encoding: UrlEncoding = UrlEncoding.All): Url = Url { - val scanner = Scanner(value) - scanner.requireAndSkip("://") { scheme = Scheme.parse(it) } + public fun parse(value: String, encoding: UrlEncoding = UrlEncoding.All): Url = try { + Url { + val scanner = Scanner(value) + scanner.requireAndSkip("://") { scheme = Scheme.parse(it) } - scanner.optionalAndSkip("@") { - userInfo.parseEncoded(it) - } + scanner.optionalAndSkip("@") { + userInfo.parseEncoded(it) + } - scanner.upToOrEnd("/", "?", "#") { authority -> - val (h, p) = authority.parseHostPort() - host = h - p?.let { port = it } - } + scanner.upToOrEnd("/", "?", "#") { authority -> + val (h, p) = authority.parseHostPort() + host = h + p?.let { port = it } + } - scanner.ifStartsWith("/") { - scanner.upToOrEnd("?", "#") { - path.parse(it, encoding) + scanner.ifStartsWith("/") { + scanner.upToOrEnd("?", "#") { + path.parse(it, encoding) + } } - } - scanner.ifStartsWith("?") { - scanner.upToOrEnd("#") { - parameters.parse(it, encoding) + scanner.ifStartsWith("?") { + scanner.upToOrEnd("#") { + parameters.parse(it, encoding) + } } - } - scanner.ifStartsWithSkip("#") { - scanner.upToOrEnd { parseFragment(it, encoding) } + scanner.ifStartsWithSkip("#") { + scanner.upToOrEnd { parseFragment(it, encoding) } + } } + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("Cannot parse \"$value\" as a URL", e) } private fun stringify( diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt index b7f5e30dd..162a8e63d 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt @@ -8,7 +8,6 @@ package aws.smithy.kotlin.runtime.client.endpoints import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.collections.ValuesMap import aws.smithy.kotlin.runtime.net.url.Url -import aws.smithy.kotlin.runtime.util.AttributeKey import aws.smithy.kotlin.runtime.util.Attributes import aws.smithy.kotlin.runtime.util.emptyAttributes From afc6c2640fc3d8a4ca2388b04ddc4eb207991d0e Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Sat, 18 Nov 2023 00:19:49 +0000 Subject: [PATCH 10/17] Fixing bugs with presigning for S3 & Polly --- .../rendering/protocol/HttpBindingProtocolGenerator.kt | 4 ++-- .../kotlin/codegen/IdempotentTokenGeneratorTest.kt | 2 +- .../protocol/HttpBindingProtocolGeneratorTest.kt | 10 +++++----- .../smithy/kotlin/runtime/auth/awssigning/Presigner.kt | 1 - 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index 0e09f7c80..c951a0694 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -344,7 +344,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { } if (segment.isGreedyLabel) { - write("addAll(#S.split(#S))", "\${$identifier}", '"') + write("addAll(#S.split(#S))", "\${$identifier}", '/') } else { write("add(#S)", "\${$identifier}") } @@ -388,7 +388,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { if (queryBindings.isEmpty() && queryLiterals.isEmpty() && queryMapBindings.isEmpty()) return - writer.withBlock("parameters.encodedParameters {", "}") { + writer.withBlock("parameters.decodedParameters {", "}") { queryLiterals.forEach { (key, value) -> writer.write("add(#S, #S)", key, value) } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt index d8576b119..8888a1334 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt @@ -64,7 +64,7 @@ internal class AllocateWidgetQueryOperationSerializer: HttpSerialize { builder.url { path.decodedSegments { add("smoketest") - addAll("$label1".split("\"")) + addAll("$label1".split("/")) add("foo") } - parameters.encodedParameters { + parameters.decodedParameters { if (input.query1 != null) add("Query1", input.query1) } } @@ -260,7 +260,7 @@ internal class TimestampInputOperationSerializer: HttpSerialize Date: Sun, 19 Nov 2023 02:43:47 +0000 Subject: [PATCH 11/17] Changes necessary to pass Smithy protocol tests --- .../kotlin/codegen/core/RuntimeTypes.kt | 1 + .../protocol/HttpBindingProtocolGenerator.kt | 11 ++++--- .../HttpBindingProtocolGeneratorTest.kt | 30 +++++++++---------- runtime/runtime-core/api/runtime-core.api | 2 ++ .../smithy/kotlin/runtime/net/url/UrlPath.kt | 12 ++++++-- .../runtime/text/encoding/PercentEncoding.kt | 7 ++++- 6 files changed, 40 insertions(+), 23 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 8f7dc0082..8f389dda0 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -151,6 +151,7 @@ object RuntimeTypes { val decodeBase64Bytes = symbol("decodeBase64Bytes") val encodeBase64 = symbol("encodeBase64") val encodeBase64String = symbol("encodeBase64String") + val PercentEncoding = symbol("PercentEncoding") } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index c951a0694..fbf677ab8 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -314,7 +314,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { renderNonBlankGuard(ctx, binding.member, writer) } - writer.withBlock("path.decodedSegments {", "}") { + writer.withBlock("path.encodedSegments {", "}") { httpTrait.uri.segments.forEach { segment -> if (segment.isLabel || segment.isGreedyLabel) { // spec dictates member name and label name MUST be the same @@ -343,14 +343,17 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { "input.${binding.member.defaultName()}" } + val encodeFn = format("#T.SmithyLabel.encode", RuntimeTypes.Core.Text.Encoding.PercentEncoding) + if (segment.isGreedyLabel) { - write("addAll(#S.split(#S))", "\${$identifier}", '/') + write("#S.split(#S).mapTo(this) { #L(it) }", "\${$identifier}", '/', encodeFn) } else { - write("add(#S)", "\${$identifier}") + write("add(#L(#S))", encodeFn, "\${$identifier}") } } else { // literal - writer.write("add(\"#L\")", segment.content.toEscapedLiteral()) + val encodeFn = format("#T.Path.encode", RuntimeTypes.Core.Text.Encoding.PercentEncoding) + writer.write("add(#L(\"#L\"))", encodeFn, segment.content.toEscapedLiteral()) } } } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt index bf5e632f7..48504e603 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt @@ -47,10 +47,10 @@ internal class SmokeTestOperationSerializer: HttpSerialize { builder.method = HttpMethod.POST builder.url { - path.decodedSegments { - add("smoketest") - addAll("$label1".split("/")) - add("foo") + path.encodedSegments { + add(PercentEncoding.Path.encode("smoketest")) + "$label1".split("/").mapTo(this) { PercentEncoding.SmithyLabel.encode(it) } + add(PercentEncoding.Path.encode("foo")) } parameters.decodedParameters { if (input.query1 != null) add("Query1", input.query1) @@ -255,10 +255,10 @@ internal class TimestampInputOperationSerializer: HttpSerialize (Ljava/util/List;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z + public final fun getDecoded ()Ljava/lang/String; + public final fun getEncoded ()Ljava/lang/String; public final fun getSegments ()Ljava/util/List; public final fun getTrailingSlash ()Z public fun hashCode ()I diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt index 7a30e422e..12061598b 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt @@ -83,7 +83,13 @@ public class UrlPath private constructor( return result } - override fun toString(): String = asEncoded(segments, trailingSlash) + public val decoded: String + get() = asDecoded(segments, trailingSlash) + + public val encoded: String + get() = asEncoded(segments, trailingSlash) + + override fun toString(): String = encoded /** * A mutable builder used to construct [UrlPath] instances @@ -115,7 +121,7 @@ public class UrlPath private constructor( */ public val decodedSegments: MutableList = segments.asView( Encodable::decoded, - PercentEncoding.Query::encodableFromDecoded, + PercentEncoding.Path::encodableFromDecoded, ) public fun decodedSegments(block: MutableList.() -> Unit) { @@ -127,7 +133,7 @@ public class UrlPath private constructor( */ public val encodedSegments: MutableList = segments.asView( Encodable::encoded, - PercentEncoding.Query::encodableFromEncoded, + PercentEncoding.Path::encodableFromEncoded, ) public fun encodedSegments(block: MutableList.() -> Unit) { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt index ba07ad12c..e79393e62 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt @@ -17,7 +17,7 @@ public class PercentEncoding( private val ALPHA = (('A'..'Z') + ('a'..'z')).toSet() private val DIGIT = ('0'..'9').toSet() private val UNRESERVED = ALPHA + DIGIT + setOf('-', '.', '_', '~') - private val SUB_DELIMS = setOf('!', '$', '&', '\'', '(', ')', '*', ',', ';', '=') + private val SUB_DELIMS = setOf('!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=') private val VALID_UCHAR = UNRESERVED + SUB_DELIMS private val VALID_PCHAR = VALID_UCHAR + setOf(':', '@') private val VALID_FCHAR = VALID_PCHAR + setOf('/', '?') @@ -26,6 +26,10 @@ public class PercentEncoding( // what MUST be encoded in queries: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html private val VALID_QCHAR = UNRESERVED + // Undocumented formally but crafted to pass Smithy protocol tests, most especially this one: + // https://github.com/smithy-lang/smithy/blob/d457aabb80feb4088caa3ac27d337b84e3ebc43d/smithy-aws-protocol-tests/model/restXml/http-labels.smithy#L42-L59 + private val SMITHY_LABEL_CHAR = UNRESERVED + private const val UPPER_HEX = "0123456789ABCDEF" public val Host: Encoding = PercentEncoding("host", UNRESERVED + ':') // e.g., for IPv6 zone ID encoding @@ -34,6 +38,7 @@ public class PercentEncoding( public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR) public val Fragment: Encoding = PercentEncoding("fragment", VALID_FCHAR) public val FormUrl: Encoding = PercentEncoding("form URL", VALID_QCHAR, mapOf(' ' to '+')) + public val SmithyLabel: Encoding = PercentEncoding("Smithy label", SMITHY_LABEL_CHAR) private fun percentAsciiEncode(char: Char) = buildString { val value = char.code and 0xff From 0ac6ca35011069aa7dcbd3eb4983df089803651c Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Sun, 19 Nov 2023 04:36:45 +0000 Subject: [PATCH 12/17] Update endpoint discovery; add a copy function for Url --- .../endpoints/discovery/EndpointDiscovererGenerator.kt | 2 +- .../endpoints/discovery/EndpointDiscovererGeneratorTest.kt | 2 +- .../common/src/aws/smithy/kotlin/runtime/net/url/Url.kt | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt index 6167cd972..f7edb2ddf 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt @@ -92,7 +92,7 @@ class EndpointDiscovererGenerator(private val ctx: CodegenContext, private val d write("") write("val originalEndpoint = delegate.resolve(request)") withBlock("#T(", ")", RuntimeTypes.SmithyClient.Endpoints.Endpoint) { - write("originalEndpoint.uri.copy(host = discoveredHost),") + write("originalEndpoint.uri.copy { host = discoveredHost },") write("originalEndpoint.headers,") write("originalEndpoint.attributes,") } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGeneratorTest.kt index 8d7a725a1..b67c51d57 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGeneratorTest.kt @@ -46,7 +46,7 @@ class EndpointDiscovererGeneratorTest { val originalEndpoint = delegate.resolve(request) Endpoint( - originalEndpoint.uri.copy(host = discoveredHost), + originalEndpoint.uri.copy { host = discoveredHost }, originalEndpoint.headers, originalEndpoint.attributes, ) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt index 87c07989b..e7195a01a 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt @@ -137,6 +137,8 @@ public class Url private constructor( */ public fun toBuilder(): Builder = Builder(this) + public fun copy(block: Builder.() -> Unit = { }): Url = toBuilder().apply(block).build() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false From 5de290de5ebffa4996c7c703317dcc0508e7ad04 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Tue, 21 Nov 2023 06:25:43 +0000 Subject: [PATCH 13/17] addressing PR feedback --- .../kotlin/codegen/core/RuntimeTypes.kt | 19 ++- .../AuthSchemeProviderAdapterGenerator.kt | 2 +- .../DefaultEndpointProviderGenerator.kt | 4 +- .../DefaultEndpointProviderTestGenerator.kt | 4 +- .../EndpointResolverAdapterGenerator.kt | 4 +- .../discovery/EndpointDiscovererGenerator.kt | 4 +- .../protocol/HttpBindingProtocolGenerator.kt | 152 ++++++++++-------- .../protocol/HttpStringValuesMapSerializer.kt | 2 +- .../codegen/IdempotentTokenGeneratorTest.kt | 10 +- .../HttpBindingProtocolGeneratorTest.kt | 38 +++-- .../HttpStringValuesMapSerializerTest.kt | 8 +- .../aws-credentials/api/aws-credentials.api | 14 +- .../CachedCredentialsProvider.kt | 2 +- .../auth/awscredentials/Credentials.kt | 2 +- .../awscredentials/CredentialsProvider.kt | 2 +- .../CredentialsProviderChain.kt | 2 +- .../CachedCredentialsProviderTest.kt | 2 +- .../CredentialsProviderChainTest.kt | 2 +- .../api/aws-signing-common.api | 22 +-- .../auth/awssigning/AwsSigningAttributes.kt | 2 +- .../runtime/auth/awssigning/Presigner.kt | 2 +- .../runtime/auth/awssigning/PresignerTest.kt | 2 +- .../runtime/auth/awssigning/Canonicalizer.kt | 14 +- .../runtime/auth/awssigning/RequestMutator.kt | 10 +- .../auth/awssigning/tests/SigningTestUtil.kt | 2 +- .../tests/SigningSuiteTestBaseJVM.kt | 4 +- .../auth/http-auth-api/api/http-auth-api.api | 10 +- .../kotlin/runtime/http/auth/HttpSigner.kt | 2 +- .../kotlin/runtime/http/auth/AwsHttpSigner.kt | 2 +- .../kotlin/runtime/http/auth/EndpointAuth.kt | 3 +- .../http/auth/SigV4AsymmetricAuthScheme.kt | 4 +- .../runtime/http/auth/SigV4AuthScheme.kt | 5 +- .../http/auth/AwsHttpSignerTestBase.kt | 4 +- .../runtime/http/auth/EndpointAuthTest.kt | 3 +- runtime/auth/http-auth/api/http-auth.api | 8 +- .../runtime/http/auth/AnonymousAuthScheme.kt | 4 +- .../runtime/http/auth/BearerTokenProvider.kt | 2 +- .../http/auth/BearerTokenProviderChain.kt | 2 +- .../http/auth/BearerTokenProviderChainTest.kt | 4 +- .../http/auth/BearerTokenSignerTest.kt | 4 +- .../auth/identity-api/api/identity-api.api | 14 +- .../smithy/kotlin/runtime/auth/AuthOption.kt | 4 +- .../kotlin/runtime/identity/Identity.kt | 2 +- .../runtime/identity/IdentityAttributes.kt | 2 +- .../runtime/identity/IdentityProvider.kt | 4 +- .../runtime/identity/IdentityProviderChain.kt | 2 +- .../identity/IdentityProviderChainTest.kt | 4 +- .../telemetry-api/api/telemetry-api.api | 32 ++-- .../kotlin/runtime/telemetry/metrics/Gauge.kt | 4 +- .../runtime/telemetry/metrics/Histogram.kt | 4 +- .../telemetry/metrics/MonotonicCounter.kt | 4 +- .../telemetry/metrics/NoOpMeterProvider.kt | 2 +- .../telemetry/metrics/UpDownCounter.kt | 4 +- .../trace/CoroutineContextTraceExt.kt | 4 +- .../telemetry/trace/NoOpTracerProvider.kt | 4 +- .../runtime/telemetry/trace/TraceSpan.kt | 6 +- .../runtime/telemetry/trace/TraceSpanExt.kt | 2 +- .../kotlin/runtime/telemetry/trace/Tracer.kt | 4 +- .../runtime/telemetry/otel/AttributeUtils.kt | 6 +- .../telemetry/otel/OtelMeterProvider.kt | 2 +- .../telemetry/otel/OtelTracerProvider.kt | 4 +- .../runtime/telemetry/otel/AttributeTests.kt | 4 +- .../eventstream/EventStreamSigning.kt | 2 +- .../eventstream/EventStreamSigningTest.kt | 2 +- .../awsprotocol/json/AwsJsonProtocol.kt | 2 +- .../awsprotocol/json/AwsJsonProtocolTest.kt | 2 +- .../awsprotocol/ClockSkewInterceptor.kt | 2 +- .../runtime/awsprotocol/ProtocolErrors.kt | 2 +- .../http/engine/okhttp/MetricsInterceptor.kt | 4 +- .../runtime/http/engine/okhttp/OkHttpUtils.kt | 7 +- .../engine/okhttp/MetricsInterceptorTest.kt | 2 +- .../runtime/http/engine/EngineAttributes.kt | 2 +- .../http/engine/internal/HttpClientMetrics.kt | 4 +- .../FlexibleChecksumsResponseInterceptor.kt | 2 +- .../OperationTelemetryInterceptor.kt | 5 +- .../http/operation/HttpOperationContext.kt | 4 +- .../http/operation/OperationTelemetry.kt | 2 +- .../http/operation/SdkHttpOperation.kt | 3 +- .../http/operation/SdkOperationExecution.kt | 6 +- .../DiscoveredEndpointErrorInterceptorTest.kt | 2 +- ...FlexibleChecksumsRequestInterceptorTest.kt | 2 +- ...lexibleChecksumsResponseInterceptorTest.kt | 2 +- .../Md5ChecksumInterceptorTest.kt | 2 +- .../http/middleware/MutateHeadersTest.kt | 2 +- .../http/middleware/RetryMiddlewareTest.kt | 2 +- .../runtime/http/operation/AuthHandlerTest.kt | 8 +- runtime/runtime-core/api/runtime-core.api | 140 ++++++++-------- .../aws/smithy/kotlin/runtime/Exceptions.kt | 6 +- .../{util => collections}/Attributes.kt | 2 +- .../CaseInsensitiveMap.kt | 32 ++-- .../kotlin/runtime/collections/Entry.kt | 2 +- .../kotlin/runtime/collections/MultiMap.kt | 25 ++- .../runtime/collections/MutableMultiMap.kt | 105 +++++++++++- .../{util => collections}/ReadThroughCache.kt | 3 +- .../runtime/{util => collections}/Stack.kt | 2 +- .../kotlin/runtime/collections/ValuesMap.kt | 1 - .../kotlin/runtime/net/url/QueryParameters.kt | 64 +++++--- .../aws/smithy/kotlin/runtime/net/url/Url.kt | 28 ++++ .../smithy/kotlin/runtime/net/url/UrlPath.kt | 17 ++ .../smithy/kotlin/runtime/net/url/UserInfo.kt | 13 ++ .../runtime/operation/ExecutionContext.kt | 4 +- .../kotlin/runtime/text/encoding/Encodable.kt | 21 +++ .../kotlin/runtime/text/encoding/Encoding.kt | 26 ++- .../runtime/text/encoding/PercentEncoding.kt | 41 ++++- .../{util => collections}/AttributesTest.kt | 2 +- .../CaseInsensitiveMapTest.kt | 2 +- .../ReadThroughCacheTest.kt | 3 +- .../runtime/net/url/QueryParametersTest.kt | 2 +- .../kotlin/runtime/net/url/UrlParsingTest.kt | 4 +- .../kotlin/runtime/serde/json/JsonEncoder.kt | 2 +- .../kotlin/runtime/serde/json/JsonLexer.kt | 4 +- .../kotlin/runtime/serde/xml/XmlSerializer.kt | 2 +- .../kotlin/runtime/serde/xml/dom/XmlNode.kt | 5 +- runtime/smithy-client/api/smithy-client.api | 18 +-- .../kotlin/runtime/client/SdkClientOption.kt | 2 +- .../runtime/client/endpoints/Endpoint.kt | 4 +- .../client/endpoints/SigningContext.kt | 2 +- .../runtime/smithy/test/HttpRequestTest.kt | 2 +- .../smithy/test/HttpRequestTestBuilderTest.kt | 2 +- 119 files changed, 751 insertions(+), 416 deletions(-) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => collections}/Attributes.kt (99%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => collections}/CaseInsensitiveMap.kt (73%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => collections}/ReadThroughCache.kt (97%) rename runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/{util => collections}/Stack.kt (96%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => collections}/AttributesTest.kt (98%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => collections}/CaseInsensitiveMapTest.kt (92%) rename runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/{util => collections}/ReadThroughCacheTest.kt (95%) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt index 8f389dda0..377c68afc 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt @@ -101,6 +101,17 @@ object RuntimeTypes { val TimestampFormat = symbol("TimestampFormat", "time") val ClientException = symbol("ClientException") + object Collections : RuntimeTypePackage(KotlinDependency.CORE, "collections") { + val Attributes = symbol("Attributes") + val attributesOf = symbol("attributesOf") + val AttributeKey = symbol("AttributeKey") + val get = symbol("get") + val mutableMultiMapOf = symbol("mutableMultiMapOf") + val putIfAbsent = symbol("putIfAbsent") + val putIfAbsentNotNull = symbol("putIfAbsentNotNull") + val ReadThroughCache = symbol("ReadThroughCache") + } + object Content : RuntimeTypePackage(KotlinDependency.CORE, "content") { val BigDecimal = symbol("BigDecimal") val BigInteger = symbol("BigInteger") @@ -156,18 +167,10 @@ object RuntimeTypes { } object Utils : RuntimeTypePackage(KotlinDependency.CORE, "util") { - val Attributes = symbol("Attributes") - val MutableAttributes = symbol("MutableAttributes") - val attributesOf = symbol("attributesOf") - val AttributeKey = symbol("AttributeKey") val ExpiringValue = symbol("ExpiringValue") val flattenIfPossible = symbol("flattenIfPossible") - val get = symbol("get") val LazyAsyncValue = symbol("LazyAsyncValue") val length = symbol("length") - val putIfAbsent = symbol("putIfAbsent") - val putIfAbsentNotNull = symbol("putIfAbsentNotNull") - val ReadThroughCache = symbol("ReadThroughCache") val truthiness = symbol("truthiness") val toNumber = symbol("toNumber") val type = symbol("type") diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeProviderAdapterGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeProviderAdapterGenerator.kt index 4b3528e71..1e84d6688 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeProviderAdapterGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/AuthSchemeProviderAdapterGenerator.kt @@ -46,7 +46,7 @@ class AuthSchemeProviderAdapterGenerator { RuntimeTypes.Auth.Identity.AuthOption, ) { withBlock("val params = #T {", "}", AuthSchemeParametersGenerator.getSymbol(ctx.settings)) { - addImport(RuntimeTypes.Core.Utils.get) + addImport(RuntimeTypes.Core.Collections.get) write("operationName = request.context[#T.OperationName]", RuntimeTypes.SmithyClient.SdkClientOption) if (ctx.settings.api.enableEndpointAuthProvider) { diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt index 1e1e764d6..57de6859f 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt @@ -168,7 +168,7 @@ class DefaultEndpointProviderGenerator( } if (rule.endpoint.properties.isNotEmpty()) { - withBlock("attributes = #T {", "},", RuntimeTypes.Core.Utils.attributesOf) { + withBlock("attributes = #T {", "},", RuntimeTypes.Core.Collections.attributesOf) { rule.endpoint.properties.entries.forEach { (k, v) -> val kStr = k.toString() @@ -180,7 +180,7 @@ class DefaultEndpointProviderGenerator( // otherwise, we just traverse the value like any other rules expression, object values will // be rendered as Documents - writeInline("#T(#S) to ", RuntimeTypes.Core.Utils.AttributeKey, kStr) + writeInline("#T(#S) to ", RuntimeTypes.Core.Collections.AttributeKey, kStr) renderExpression(v) ensureNewline() } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt index e687b97d3..978f7dc80 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt @@ -119,14 +119,14 @@ class DefaultEndpointProviderTestGenerator( } if (endpoint.properties.isNotEmpty()) { - withBlock("attributes = #T {", "},", RuntimeTypes.Core.Utils.attributesOf) { + withBlock("attributes = #T {", "},", RuntimeTypes.Core.Collections.attributesOf) { endpoint.properties.entries.forEach { (k, v) -> if (k in expectedPropertyRenderers) { expectedPropertyRenderers[k]!!(writer, Expression.fromNode(v), this@DefaultEndpointProviderTestGenerator) return@forEach } - writeInline("#T(#S) to ", RuntimeTypes.Core.Utils.AttributeKey, k) + writeInline("#T(#S) to ", RuntimeTypes.Core.Collections.AttributeKey, k) renderExpression(Expression.fromNode(v)) ensureNewline() } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterGenerator.kt index 03d51443d..df94825ec 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/EndpointResolverAdapterGenerator.kt @@ -114,7 +114,7 @@ class EndpointResolverAdapterGenerator( RuntimeTypes.HttpClient.Operation.ResolveEndpointRequest, EndpointParametersGenerator.getSymbol(ctx.settings), ) { - writer.addImport(RuntimeTypes.Core.Utils.get) + writer.addImport(RuntimeTypes.Core.Collections.get) withBlock("return #T {", "}", EndpointParametersGenerator.getSymbol(ctx.settings)) { // The SEP dictates a specific source order to use when binding parameters (from most specific to least): // 1. staticContextParams (from operation shape) @@ -164,7 +164,7 @@ class EndpointResolverAdapterGenerator( val inputContextParams = epParameterIndex.inputContextParams(op) if (inputContextParams.isNotEmpty()) { - writer.addImport(RuntimeTypes.Core.Utils.get) + writer.addImport(RuntimeTypes.Core.Collections.get) writer.write("@Suppress(#S)", "UNCHECKED_CAST") val opInputShape = ctx.model.expectShape(op.inputShape) val inputSymbol = ctx.symbolProvider.toSymbol(opInputShape) diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt index f7edb2ddf..c492cd743 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/discovery/EndpointDiscovererGenerator.kt @@ -51,7 +51,7 @@ class EndpointDiscovererGenerator(private val ctx: CodegenContext, private val d ) { write( "private val cache = #T(10.#T, #T.System)", - RuntimeTypes.Core.Utils.ReadThroughCache, + RuntimeTypes.Core.Collections.ReadThroughCache, RuntimeTypes.Core.Net.Host, KotlinTypes.Time.minutes, RuntimeTypes.Core.Clock, @@ -66,7 +66,7 @@ class EndpointDiscovererGenerator(private val ctx: CodegenContext, private val d write("") write( """private val discoveryParamsKey = #T("DiscoveryParams")""", - RuntimeTypes.Core.Utils.AttributeKey, + RuntimeTypes.Core.Collections.AttributeKey, ) write("private data class DiscoveryParams(private val region: String?, private val identity: String)") } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index fbf677ab8..a93bc1236 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -314,46 +314,50 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { renderNonBlankGuard(ctx, binding.member, writer) } - writer.withBlock("path.encodedSegments {", "}") { - httpTrait.uri.segments.forEach { segment -> - if (segment.isLabel || segment.isGreedyLabel) { - // spec dictates member name and label name MUST be the same - val binding = pathBindings.find { binding -> - binding.memberName == segment.content - } ?: throw CodegenException("failed to find corresponding member for httpLabel `${segment.content}") - - // shape must be string, number, boolean, or timestamp - val targetShape = ctx.model.expectShape(binding.member.target) - val memberSymbol = ctx.symbolProvider.toSymbol(binding.member) - val identifier = if (targetShape.isTimestampShape) { - addImport(RuntimeTypes.Core.TimestampFormat) - val tsFormat = resolver.determineTimestampFormat( - binding.member, - HttpBinding.Location.LABEL, - defaultTimestampFormat, - ) - val nullCheck = if (memberSymbol.isNullable) "?" else "" - val tsLabel = formatInstant( - "input.${binding.member.defaultName()}$nullCheck", - tsFormat, - forceString = true, - ) - tsLabel - } else { - "input.${binding.member.defaultName()}" - } + if (httpTrait.uri.segments.isNotEmpty()) { + writer.withBlock("path.encodedSegments {", "}") { + httpTrait.uri.segments.forEach { segment -> + if (segment.isLabel || segment.isGreedyLabel) { + // spec dictates member name and label name MUST be the same + val binding = pathBindings.find { binding -> + binding.memberName == segment.content + } + ?: throw CodegenException("failed to find corresponding member for httpLabel `${segment.content}") + + // shape must be string, number, boolean, or timestamp + val targetShape = ctx.model.expectShape(binding.member.target) + val memberSymbol = ctx.symbolProvider.toSymbol(binding.member) + val identifier = if (targetShape.isTimestampShape) { + addImport(RuntimeTypes.Core.TimestampFormat) + val tsFormat = resolver.determineTimestampFormat( + binding.member, + HttpBinding.Location.LABEL, + defaultTimestampFormat, + ) + val nullCheck = if (memberSymbol.isNullable) "?" else "" + val tsLabel = formatInstant( + "input.${binding.member.defaultName()}$nullCheck", + tsFormat, + forceString = true, + ) + tsLabel + } else { + "input.${binding.member.defaultName()}" + } - val encodeFn = format("#T.SmithyLabel.encode", RuntimeTypes.Core.Text.Encoding.PercentEncoding) + val encodeFn = + format("#T.SmithyLabel.encode", RuntimeTypes.Core.Text.Encoding.PercentEncoding) - if (segment.isGreedyLabel) { - write("#S.split(#S).mapTo(this) { #L(it) }", "\${$identifier}", '/', encodeFn) + if (segment.isGreedyLabel) { + write("#S.split(#S).mapTo(this) { #L(it) }", "\${$identifier}", '/', encodeFn) + } else { + write("add(#L(#S))", encodeFn, "\${$identifier}") + } } else { - write("add(#L(#S))", encodeFn, "\${$identifier}") + // literal + val encodeFn = format("#T.Path.encode", RuntimeTypes.Core.Text.Encoding.PercentEncoding) + writer.write("add(#L(\"#L\"))", encodeFn, segment.content.toEscapedLiteral()) } - } else { - // literal - val encodeFn = format("#T.Path.encode", RuntimeTypes.Core.Text.Encoding.PercentEncoding) - writer.write("add(#L(\"#L\"))", encodeFn, segment.content.toEscapedLiteral()) } } } @@ -391,42 +395,60 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { if (queryBindings.isEmpty() && queryLiterals.isEmpty() && queryMapBindings.isEmpty()) return - writer.withBlock("parameters.decodedParameters {", "}") { - queryLiterals.forEach { (key, value) -> - writer.write("add(#S, #S)", key, value) + if (queryLiterals.isNotEmpty()) { + writer.withBlock("parameters.decodedParameters {", "}") { + queryLiterals.forEach { (key, value) -> writer.write("add(#S, #S)", key, value) } } + } - // render length check if applicable - queryBindings.forEach { binding -> renderNonBlankGuard(ctx, binding.member, writer) } + if (queryBindings.isNotEmpty() || queryMapBindings.isNotEmpty()) { + writer.withBlock("parameters.encodedParameters {", "}") { + // Set up a temporary map for the query labels + writer.write("val labels = #T()", RuntimeTypes.Core.Collections.mutableMultiMapOf) + + // render length check if applicable + queryBindings.forEach { binding -> renderNonBlankGuard(ctx, binding.member, writer) } + + renderStringValuesMapParameters(ctx, queryBindings, writer) + + queryMapBindings.forEach { + // either Map or Map> + // https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#httpqueryparams-trait + val target = ctx.model.expectShape(it.member.target) + val valueTarget = ctx.model.expectShape(target.value.target) + val fn = when (valueTarget.type) { + ShapeType.STRING -> "labels.add" + ShapeType.LIST, ShapeType.SET -> "labels.addAll" + else -> throw CodegenException("unexpected value type for httpQueryParams map") + } - renderStringValuesMapParameters(ctx, queryBindings, writer) + val nullCheck = if (target.hasTrait()) { + "if (value != null) " + } else { + "" + } - queryMapBindings.forEach { - // either Map or Map> - // https://awslabs.github.io/smithy/1.0/spec/core/http-traits.html#httpqueryparams-trait - val target = ctx.model.expectShape(it.member.target) - val valueTarget = ctx.model.expectShape(target.value.target) - val fn = when (valueTarget.type) { - ShapeType.STRING -> "add" - ShapeType.LIST, ShapeType.SET -> "addAll" - else -> throw CodegenException("unexpected value type for httpQueryParams map") + writer.write("input.${it.member.defaultName()}") + .indent() + // ensure query precedence rules are enforced by filtering keys already set + // (httpQuery bound members take precedence over a query map with same key) + .write("?.filterNot{ contains(it.key) }") + .withBlock("?.forEach { (key, value) ->", "}") { + write("${nullCheck}$fn(key, value)") + } + .dedent() } - val nullCheck = if (target.hasTrait()) { - "if (value != null) " - } else { - "" + // Transfer encoded labels into query params map + openBlock("labels") + write(".entries") + withBlock(".associateTo(this) { (key, values) ->", "}") { + write( + "#1T.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), #1T.SmithyLabel::encode)", + RuntimeTypes.Core.Text.Encoding.PercentEncoding, + ) } - - writer.write("input.${it.member.defaultName()}") - .indent() - // ensure query precedence rules are enforced by filtering keys already set - // (httpQuery bound members take precedence over a query map with same key) - .write("?.filterNot{ contains(it.key) }") - .withBlock("?.forEach { (key, value) ->", "}") { - write("${nullCheck}$fn(key, value)") - } - .dedent() + dedent() } } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt index c82be5333..c53bd13bb 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt @@ -215,7 +215,7 @@ class HttpStringValuesMapSerializer( private val HttpBinding.Location.addFnName: String get() = when (this) { - HttpBinding.Location.QUERY, HttpBinding.Location.QUERY_PARAMS -> "add" // uses MutableMultiMap + HttpBinding.Location.QUERY, HttpBinding.Location.QUERY_PARAMS -> "labels.add" // uses MutableMultiMap else -> "append" // uses ValuesMapBuilder } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt index 8888a1334..936052944 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt @@ -64,8 +64,14 @@ internal class AllocateWidgetQueryOperationSerializer: HttpSerialize() + labels.add("clientToken", (input.clientToken ?: context.idempotencyTokenProvider.generateToken())) + labels + .entries + .associateTo(this) { (key, values) -> + PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) + } } } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt index 48504e603..cab6b01e7 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt @@ -52,8 +52,14 @@ internal class SmokeTestOperationSerializer: HttpSerialize { "$label1".split("/").mapTo(this) { PercentEncoding.SmithyLabel.encode(it) } add(PercentEncoding.Path.encode("foo")) } - parameters.decodedParameters { - if (input.query1 != null) add("Query1", input.query1) + parameters.encodedParameters { + val labels = mutableMultiMapOf() + if (input.query1 != null) labels.add("Query1", input.query1) + labels + .entries + .associateTo(this) { (key, values) -> + PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) + } } } @@ -260,9 +266,15 @@ internal class TimestampInputOperationSerializer: HttpSerialize() + if (input.queryTimestamp != null) labels.add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) + if (input.queryTimestampList?.isNotEmpty() == true) labels.addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) + labels + .entries + .associateTo(this) { (key, values) -> + PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) + } } } @@ -565,12 +577,18 @@ internal class SmokeTestOperationDeserializer: HttpDeserialize() require(input.garply?.isNotBlank() == true) { "garply is bound to the URI and must be a non-blank value" } - if (input.corge != null) add("corge", input.corge) - if (input.garply != null) add("garply", input.garply) - if (input.grault != null) add("grault", input.grault) - if (input.quux != null) add("quux", "$quux") + if (input.corge != null) labels.add("corge", input.corge) + if (input.garply != null) labels.add("garply", input.garply) + if (input.grault != null) labels.add("grault", input.grault) + if (input.quux != null) labels.add("quux", "$quux") + labels + .entries + .associateTo(this) { (key, values) -> + PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) + } } """ diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt index 285fa1414..4b0ccf243 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt @@ -72,7 +72,7 @@ class HttpStringValuesMapSerializerTest { contents.assertBalancedBracesAndParens() val expectedContents = """ - if (input.qInt != 0) add("q-int", "${'$'}{input.qInt}") + if (input.qInt != 0) labels.add("q-int", "${'$'}{input.qInt}") """.trimIndent() contents.shouldContainOnlyOnceWithDiff(expectedContents) } @@ -84,7 +84,7 @@ class HttpStringValuesMapSerializerTest { contents.assertBalancedBracesAndParens() val expectedContents = """ - add("q-int", "${'$'}{input.qInt}") + labels.add("q-int", "${'$'}{input.qInt}") """.trimIndent() contents.shouldContainOnlyOnceWithDiff(expectedContents) } @@ -190,8 +190,8 @@ class HttpStringValuesMapSerializerTest { val queryContents = getTestContents(defaultModel, "com.test#TimestampInput", HttpBinding.Location.QUERY) val expectedQueryContents = """ - if (input.queryTimestamp != null) add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) - if (input.queryTimestampList?.isNotEmpty() == true) addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) + if (input.queryTimestamp != null) labels.add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) + if (input.queryTimestampList?.isNotEmpty() == true) labels.addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) """.trimIndent() queryContents.shouldContainOnlyOnceWithDiff(expectedQueryContents) } diff --git a/runtime/auth/aws-credentials/api/aws-credentials.api b/runtime/auth/aws-credentials/api/aws-credentials.api index 0c0e702c9..2b48dd707 100644 --- a/runtime/auth/aws-credentials/api/aws-credentials.api +++ b/runtime/auth/aws-credentials/api/aws-credentials.api @@ -2,7 +2,7 @@ public final class aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentia public synthetic fun (Laws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider;JJLaws/smithy/kotlin/runtime/time/Clock;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Laws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider;JJLaws/smithy/kotlin/runtime/time/Clock;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close ()V - public fun resolve (Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProviderKt { @@ -21,8 +21,8 @@ public abstract interface class aws/smithy/kotlin/runtime/auth/awscredentials/Cr } public final class aws/smithy/kotlin/runtime/auth/awscredentials/Credentials$Companion { - public final fun invoke (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; - public static synthetic fun invoke$default (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; + public final fun invoke (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; + public static synthetic fun invoke$default (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; } public final class aws/smithy/kotlin/runtime/auth/awscredentials/Credentials$DefaultImpls { @@ -31,18 +31,18 @@ public final class aws/smithy/kotlin/runtime/auth/awscredentials/Credentials$Def } public final class aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsKt { - public static final fun copy (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; + public static final fun copy (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/time/Instant;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; } public abstract interface class aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider : aws/smithy/kotlin/runtime/identity/IdentityProvider { - public abstract fun resolve (Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChain : aws/smithy/kotlin/runtime/identity/IdentityProviderChain, aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider { public fun (Ljava/util/List;)V public fun ([Laws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider;)V - public fun resolve (Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderConfig { diff --git a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProvider.kt b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProvider.kt index 571f118d8..d917408d5 100644 --- a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProvider.kt +++ b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProvider.kt @@ -5,10 +5,10 @@ package aws.smithy.kotlin.runtime.auth.awscredentials +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.io.closeIfCloseable import aws.smithy.kotlin.runtime.telemetry.logging.trace import aws.smithy.kotlin.runtime.time.Clock -import aws.smithy.kotlin.runtime.util.Attributes import aws.smithy.kotlin.runtime.util.CachedValue import aws.smithy.kotlin.runtime.util.ExpiringValue import kotlinx.atomicfu.atomic diff --git a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/Credentials.kt b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/Credentials.kt index 7a12616b5..19230fd20 100644 --- a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/Credentials.kt +++ b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/Credentials.kt @@ -4,10 +4,10 @@ */ package aws.smithy.kotlin.runtime.auth.awscredentials +import aws.smithy.kotlin.runtime.collections.* import aws.smithy.kotlin.runtime.identity.Identity import aws.smithy.kotlin.runtime.identity.IdentityAttributes import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.* /** * Represents a set of AWS credentials diff --git a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider.kt b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider.kt index 2618374ea..7c3593b6b 100644 --- a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider.kt +++ b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider.kt @@ -4,9 +4,9 @@ */ package aws.smithy.kotlin.runtime.auth.awscredentials +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.identity.IdentityProvider import aws.smithy.kotlin.runtime.io.Closeable -import aws.smithy.kotlin.runtime.util.Attributes /** * Represents a producer/source of AWS credentials diff --git a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChain.kt b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChain.kt index 49696f9ec..204c9c7cc 100644 --- a/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChain.kt +++ b/runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChain.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.auth.awscredentials +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.identity.IdentityProviderChain -import aws.smithy.kotlin.runtime.util.Attributes /** * Composite [CredentialsProvider] that delegates to a chain of providers. When asked for credentials, providers diff --git a/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProviderTest.kt b/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProviderTest.kt index fffe8f22b..1f8c8c990 100644 --- a/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProviderTest.kt +++ b/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CachedCredentialsProviderTest.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.auth.awscredentials +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.ManualClock -import aws.smithy.kotlin.runtime.util.Attributes import io.kotest.matchers.string.shouldContain import kotlinx.coroutines.test.runTest import kotlin.test.Test diff --git a/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChainTest.kt b/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChainTest.kt index 08b3954df..76f542961 100644 --- a/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChainTest.kt +++ b/runtime/auth/aws-credentials/common/test/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderChainTest.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.auth.awscredentials -import aws.smithy.kotlin.runtime.util.Attributes +import aws.smithy.kotlin.runtime.collections.Attributes import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/auth/aws-signing-common/api/aws-signing-common.api b/runtime/auth/aws-signing-common/api/aws-signing-common.api index 09ec08522..73e2e9ab9 100644 --- a/runtime/auth/aws-signing-common/api/aws-signing-common.api +++ b/runtime/auth/aws-signing-common/api/aws-signing-common.api @@ -33,17 +33,17 @@ public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAlgorithm public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttributes { public static final field INSTANCE Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttributes; - public final fun getCredentialsProvider ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getHashSpecification ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getNormalizeUriPath ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getRequestSignature ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getSignedBodyHeader ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getSigner ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getSigningDate ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getSigningRegion ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getSigningRegionSet ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getSigningService ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getUseDoubleUriEncode ()Laws/smithy/kotlin/runtime/util/AttributeKey; + public final fun getCredentialsProvider ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getHashSpecification ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getNormalizeUriPath ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getRequestSignature ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getSignedBodyHeader ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getSigner ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getSigningDate ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getSigningRegion ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getSigningRegionSet ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getSigningService ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getUseDoubleUriEncode ()Laws/smithy/kotlin/runtime/collections/AttributeKey; } public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig { diff --git a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttributes.kt b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttributes.kt index 9e039bbb9..559e5a4bd 100644 --- a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttributes.kt +++ b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttributes.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import aws.smithy.kotlin.runtime.collections.AttributeKey import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.AttributeKey import kotlinx.coroutines.CompletableDeferred /** diff --git a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt index 1df17191a..3cb0198c5 100644 --- a/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt +++ b/runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Presigner.kt @@ -8,6 +8,7 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.auth.AuthSchemeId import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.client.endpoints.authOptions +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.operation.EndpointResolver import aws.smithy.kotlin.runtime.http.operation.ResolveEndpointRequest @@ -16,7 +17,6 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.header import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext -import aws.smithy.kotlin.runtime.util.emptyAttributes @InternalApi public suspend fun presignRequest( diff --git a/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt b/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt index 1aec17b59..52407b445 100644 --- a/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt +++ b/runtime/auth/aws-signing-common/common/test/aws/smithy/kotlin/runtime/auth/awssigning/PresignerTest.kt @@ -7,6 +7,7 @@ package aws.smithy.kotlin.runtime.auth.awssigning import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.client.endpoints.Endpoint +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.operation.EndpointResolver import aws.smithy.kotlin.runtime.http.operation.ResolveEndpointRequest @@ -15,7 +16,6 @@ import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.url import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext -import aws.smithy.kotlin.runtime.util.Attributes import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt index e941da8e0..9e02d1d17 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/Canonicalizer.kt @@ -206,18 +206,14 @@ internal fun Url.Builder.canonicalPath(config: AwsSigningConfig): String { * Canonicalizes the query parameters from this [Url.Builder]. * @return The canonicalized query parameters */ -internal fun Url.Builder.canonicalQueryParams(): String { - val canonicalized = parameters +internal fun Url.Builder.canonicalQueryParams(): String = QueryParameters { + parameters .entries - .associate { (key, values) -> key.reencode().encoded to values.map { it.reencode().encoded } } // FIXME 🤮 + .associate { (key, values) -> key.reencode().encoded to values.map { it.reencode().encoded } } // re-encode all .entries .sortedWith(compareBy { it.key }) // Sort keys - .associate { (key, values) -> key to values.sorted().toMutableList() } // Sort values - - return QueryParameters { - encodedParameters.putAll(canonicalized) - }.toString().removePrefix("?") -} + .associateTo(encodedParameters) { (key, values) -> key to values.sorted().toMutableList() } // Sort values +}.toString().removePrefix("?") private fun Pair>.canonicalLine(): String { val valuesString = second.joinToString(separator = ",") { it.trimAll() } diff --git a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt index 68585391c..5bfe982e5 100644 --- a/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt +++ b/runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/RequestMutator.kt @@ -46,17 +46,9 @@ internal class DefaultRequestMutator : RequestMutator { canonical.request.headers["Authorization"] = "$ALGORITHM_NAME $credential, $signedHeaders, $signature" } - AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS -> { + AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS -> canonical.request.url.parameters.decodedParameters.put("X-Amz-Signature", signatureHex) - /* TODO don't need to reencode because their already canonicalized by `Encodable`? - entries().forEach { - remove(it.key) - appendAll(it.key, it.value.map(String::urlReencodeComponent)) - } - */ - } - else -> TODO("Support for ${config.signatureType} is not yet implemented") } diff --git a/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningTestUtil.kt b/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningTestUtil.kt index 72233ccd3..1701e7198 100644 --- a/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningTestUtil.kt +++ b/runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningTestUtil.kt @@ -7,7 +7,7 @@ package aws.smithy.kotlin.runtime.auth.awssigning.tests import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import aws.smithy.kotlin.runtime.util.Attributes +import aws.smithy.kotlin.runtime.collections.Attributes public val DEFAULT_TEST_CREDENTIALS: Credentials = Credentials("AKID", "SECRET", "SESSION") public val DEFAULT_TEST_CREDENTIALS_PROVIDER: CredentialsProvider = DEFAULT_TEST_CREDENTIALS.asStaticProvider() diff --git a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt index ac9301cbc..2e7b323dd 100644 --- a/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt +++ b/runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt @@ -8,7 +8,9 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.auth.awssigning.* +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.collections.ValuesMap +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.auth.AwsHttpSigner import aws.smithy.kotlin.runtime.http.auth.SigV4AuthScheme @@ -21,8 +23,6 @@ import aws.smithy.kotlin.runtime.identity.asIdentityProviderConfig import aws.smithy.kotlin.runtime.net.url.Url import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.get import io.ktor.http.cio.* import io.ktor.util.* import io.ktor.utils.io.* diff --git a/runtime/auth/http-auth-api/api/http-auth-api.api b/runtime/auth/http-auth-api/api/http-auth-api.api index 1b3b66ba9..75c1bb304 100644 --- a/runtime/auth/http-auth-api/api/http-auth-api.api +++ b/runtime/auth/http-auth-api/api/http-auth-api.api @@ -22,16 +22,16 @@ public abstract interface class aws/smithy/kotlin/runtime/http/auth/HttpSigner { } public final class aws/smithy/kotlin/runtime/http/auth/SignHttpRequest { - public fun (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/identity/Identity;Laws/smithy/kotlin/runtime/util/Attributes;)V + public fun (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/identity/Identity;Laws/smithy/kotlin/runtime/collections/Attributes;)V public final fun component1 ()Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder; public final fun component2 ()Laws/smithy/kotlin/runtime/identity/Identity; - public final fun component3 ()Laws/smithy/kotlin/runtime/util/Attributes; - public final fun copy (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/identity/Identity;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/http/auth/SignHttpRequest; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/http/auth/SignHttpRequest;Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/identity/Identity;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/http/auth/SignHttpRequest; + public final fun component3 ()Laws/smithy/kotlin/runtime/collections/Attributes; + public final fun copy (Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/identity/Identity;Laws/smithy/kotlin/runtime/collections/Attributes;)Laws/smithy/kotlin/runtime/http/auth/SignHttpRequest; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/http/auth/SignHttpRequest;Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder;Laws/smithy/kotlin/runtime/identity/Identity;Laws/smithy/kotlin/runtime/collections/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/http/auth/SignHttpRequest; public fun equals (Ljava/lang/Object;)Z public final fun getHttpRequest ()Laws/smithy/kotlin/runtime/http/request/HttpRequestBuilder; public final fun getIdentity ()Laws/smithy/kotlin/runtime/identity/Identity; - public final fun getSigningAttributes ()Laws/smithy/kotlin/runtime/util/Attributes; + public final fun getSigningAttributes ()Laws/smithy/kotlin/runtime/collections/Attributes; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/runtime/auth/http-auth-api/common/src/aws/smithy/kotlin/runtime/http/auth/HttpSigner.kt b/runtime/auth/http-auth-api/common/src/aws/smithy/kotlin/runtime/http/auth/HttpSigner.kt index 2aa4899ee..d406d6455 100644 --- a/runtime/auth/http-auth-api/common/src/aws/smithy/kotlin/runtime/http/auth/HttpSigner.kt +++ b/runtime/auth/http-auth-api/common/src/aws/smithy/kotlin/runtime/http/auth/HttpSigner.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.http.auth +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.identity.Identity -import aws.smithy.kotlin.runtime.util.Attributes /** * Represents a component capable of signing an HTTP request diff --git a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt index 595442737..ebd1a465b 100644 --- a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt +++ b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/AwsHttpSigner.kt @@ -13,12 +13,12 @@ import aws.smithy.kotlin.runtime.auth.awssigning.internal.setAwsChunkedHeaders import aws.smithy.kotlin.runtime.auth.awssigning.internal.useAwsChunkedEncoding import aws.smithy.kotlin.runtime.client.LogMode import aws.smithy.kotlin.runtime.client.SdkClientOption +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.get import kotlin.time.Duration /** diff --git a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/EndpointAuth.kt b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/EndpointAuth.kt index b40bcd4a6..706c02ab3 100644 --- a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/EndpointAuth.kt +++ b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/EndpointAuth.kt @@ -6,7 +6,8 @@ package aws.smithy.kotlin.runtime.http.auth import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.auth.AuthOption -import aws.smithy.kotlin.runtime.util.* +import aws.smithy.kotlin.runtime.collections.merge +import aws.smithy.kotlin.runtime.collections.toMutableAttributes /** * Merge the list of modeled auth options with the auth schemes from the resolved endpoint context. diff --git a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AsymmetricAuthScheme.kt b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AsymmetricAuthScheme.kt index 27b8d1d38..650b1cc19 100644 --- a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AsymmetricAuthScheme.kt +++ b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AsymmetricAuthScheme.kt @@ -10,10 +10,10 @@ import aws.smithy.kotlin.runtime.auth.AuthSchemeId import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAlgorithm import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAttributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes +import aws.smithy.kotlin.runtime.collections.mutableAttributes import aws.smithy.kotlin.runtime.identity.IdentityProvider import aws.smithy.kotlin.runtime.identity.IdentityProviderConfig -import aws.smithy.kotlin.runtime.util.emptyAttributes -import aws.smithy.kotlin.runtime.util.mutableAttributes /** * HTTP auth scheme for AWS signature version 4 Asymmetric diff --git a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AuthScheme.kt b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AuthScheme.kt index 388c0af76..13d13d78d 100644 --- a/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AuthScheme.kt +++ b/runtime/auth/http-auth-aws/common/src/aws/smithy/kotlin/runtime/http/auth/SigV4AuthScheme.kt @@ -11,7 +11,10 @@ import aws.smithy.kotlin.runtime.auth.AuthSchemeId import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAttributes import aws.smithy.kotlin.runtime.auth.awssigning.HashSpecification -import aws.smithy.kotlin.runtime.util.* +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.MutableAttributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes +import aws.smithy.kotlin.runtime.collections.mutableAttributes /** * HTTP auth scheme for AWS signature version 4 diff --git a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt index 8cfcd1f86..f166fb060 100644 --- a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt +++ b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/AwsHttpSignerTestBase.kt @@ -10,6 +10,8 @@ import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAttributes import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner import aws.smithy.kotlin.runtime.auth.awssigning.internal.AWS_CHUNKED_THRESHOLD +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.operation.* import aws.smithy.kotlin.runtime.http.request.HttpRequest @@ -22,8 +24,6 @@ import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.TestResult import kotlinx.coroutines.test.runTest import kotlin.test.Test diff --git a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/EndpointAuthTest.kt b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/EndpointAuthTest.kt index 8f1b072e0..288aec277 100644 --- a/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/EndpointAuthTest.kt +++ b/runtime/auth/http-auth-aws/common/test/aws/smithy/kotlin/runtime/http/auth/EndpointAuthTest.kt @@ -6,8 +6,7 @@ package aws.smithy.kotlin.runtime.http.auth import aws.smithy.kotlin.runtime.auth.AuthOption import aws.smithy.kotlin.runtime.auth.AuthSchemeId -import aws.smithy.kotlin.runtime.util.attributesOf -import aws.smithy.kotlin.runtime.util.get +import aws.smithy.kotlin.runtime.collections.attributesOf import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/auth/http-auth/api/http-auth.api b/runtime/auth/http-auth/api/http-auth.api index 574efd3f0..82934a189 100644 --- a/runtime/auth/http-auth/api/http-auth.api +++ b/runtime/auth/http-auth/api/http-auth.api @@ -12,13 +12,13 @@ public final class aws/smithy/kotlin/runtime/http/auth/AnonymousHttpSigner : aws public final class aws/smithy/kotlin/runtime/http/auth/AnonymousIdentity : aws/smithy/kotlin/runtime/identity/Identity { public static final field INSTANCE Laws/smithy/kotlin/runtime/http/auth/AnonymousIdentity; - public fun getAttributes ()Laws/smithy/kotlin/runtime/util/Attributes; + public fun getAttributes ()Laws/smithy/kotlin/runtime/collections/Attributes; public fun getExpiration ()Laws/smithy/kotlin/runtime/time/Instant; } public final class aws/smithy/kotlin/runtime/http/auth/AnonymousIdentityProvider : aws/smithy/kotlin/runtime/identity/IdentityProvider { public static final field INSTANCE Laws/smithy/kotlin/runtime/http/auth/AnonymousIdentityProvider; - public fun resolve (Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class aws/smithy/kotlin/runtime/http/auth/BearerToken : aws/smithy/kotlin/runtime/identity/Identity { @@ -33,13 +33,13 @@ public final class aws/smithy/kotlin/runtime/http/auth/BearerTokenAuthScheme : a } public abstract interface class aws/smithy/kotlin/runtime/http/auth/BearerTokenProvider : aws/smithy/kotlin/runtime/identity/IdentityProvider { - public abstract fun resolve (Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChain : aws/smithy/kotlin/runtime/identity/IdentityProviderChain, aws/smithy/kotlin/runtime/http/auth/BearerTokenProvider { public fun (Ljava/util/List;)V public fun ([Laws/smithy/kotlin/runtime/http/auth/BearerTokenProvider;)V - public fun resolve (Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderConfig { diff --git a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/AnonymousAuthScheme.kt b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/AnonymousAuthScheme.kt index 5acd374d8..f6943e1b8 100644 --- a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/AnonymousAuthScheme.kt +++ b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/AnonymousAuthScheme.kt @@ -6,12 +6,12 @@ package aws.smithy.kotlin.runtime.http.auth import aws.smithy.kotlin.runtime.auth.AuthSchemeId +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.identity.Identity import aws.smithy.kotlin.runtime.identity.IdentityProvider import aws.smithy.kotlin.runtime.identity.IdentityProviderConfig import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes /** * A no-op signer that does nothing with the request diff --git a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProvider.kt b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProvider.kt index d92490021..f1c821d46 100644 --- a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProvider.kt +++ b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProvider.kt @@ -5,10 +5,10 @@ package aws.smithy.kotlin.runtime.http.auth +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.identity.Identity import aws.smithy.kotlin.runtime.identity.IdentityProvider import aws.smithy.kotlin.runtime.io.Closeable -import aws.smithy.kotlin.runtime.util.Attributes /** * Represents a producer/source of Bearer authentication tokens diff --git a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChain.kt b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChain.kt index b9865315a..20e78e06c 100644 --- a/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChain.kt +++ b/runtime/auth/http-auth/common/src/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChain.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.http.auth +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.identity.IdentityProviderChain -import aws.smithy.kotlin.runtime.util.Attributes /** * Composite [BearerTokenProvider] that delegates to a chain of providers. When asked for identity, providers diff --git a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChainTest.kt b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChainTest.kt index c6bfe41c2..74b880293 100644 --- a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChainTest.kt +++ b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenProviderChainTest.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.http.auth +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenSignerTest.kt b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenSignerTest.kt index f76073ab2..61586da0a 100644 --- a/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenSignerTest.kt +++ b/runtime/auth/http-auth/common/test/aws/smithy/kotlin/runtime/http/auth/BearerTokenSignerTest.kt @@ -5,11 +5,11 @@ package aws.smithy.kotlin.runtime.http.auth +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.net.Scheme import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes import io.kotest.matchers.string.shouldContain import kotlinx.coroutines.test.runTest import kotlin.test.Test diff --git a/runtime/auth/identity-api/api/identity-api.api b/runtime/auth/identity-api/api/identity-api.api index 4ed513630..2c28a0923 100644 --- a/runtime/auth/identity-api/api/identity-api.api +++ b/runtime/auth/identity-api/api/identity-api.api @@ -1,11 +1,11 @@ public abstract interface class aws/smithy/kotlin/runtime/auth/AuthOption { - public abstract fun getAttributes ()Laws/smithy/kotlin/runtime/util/Attributes; + public abstract fun getAttributes ()Laws/smithy/kotlin/runtime/collections/Attributes; public abstract fun getSchemeId-DepwgT4 ()Ljava/lang/String; } public final class aws/smithy/kotlin/runtime/auth/AuthOptionKt { - public static final fun AuthOption-Jh0Pmzk (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/auth/AuthOption; - public static synthetic fun AuthOption-Jh0Pmzk$default (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/auth/AuthOption; + public static final fun AuthOption-Jh0Pmzk (Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;)Laws/smithy/kotlin/runtime/auth/AuthOption; + public static synthetic fun AuthOption-Jh0Pmzk$default (Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/auth/AuthOption; } public final class aws/smithy/kotlin/runtime/auth/AuthSchemeId { @@ -41,13 +41,13 @@ public abstract interface class aws/smithy/kotlin/runtime/identity/CloseableIden } public abstract interface class aws/smithy/kotlin/runtime/identity/Identity { - public abstract fun getAttributes ()Laws/smithy/kotlin/runtime/util/Attributes; + public abstract fun getAttributes ()Laws/smithy/kotlin/runtime/collections/Attributes; public abstract fun getExpiration ()Laws/smithy/kotlin/runtime/time/Instant; } public final class aws/smithy/kotlin/runtime/identity/IdentityAttributes { public static final field INSTANCE Laws/smithy/kotlin/runtime/identity/IdentityAttributes; - public final fun getProviderName ()Laws/smithy/kotlin/runtime/util/AttributeKey; + public final fun getProviderName ()Laws/smithy/kotlin/runtime/collections/AttributeKey; } public final class aws/smithy/kotlin/runtime/identity/IdentityAttributesKt { @@ -55,11 +55,11 @@ public final class aws/smithy/kotlin/runtime/identity/IdentityAttributesKt { } public abstract interface class aws/smithy/kotlin/runtime/identity/IdentityProvider { - public abstract fun resolve (Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class aws/smithy/kotlin/runtime/identity/IdentityProvider$DefaultImpls { - public static synthetic fun resolve$default (Laws/smithy/kotlin/runtime/identity/IdentityProvider;Laws/smithy/kotlin/runtime/util/Attributes;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun resolve$default (Laws/smithy/kotlin/runtime/identity/IdentityProvider;Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public abstract interface class aws/smithy/kotlin/runtime/identity/IdentityProviderConfig { diff --git a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/auth/AuthOption.kt b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/auth/AuthOption.kt index 6982a2916..bea3cf96d 100644 --- a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/auth/AuthOption.kt +++ b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/auth/AuthOption.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.auth -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes /** * A tuple of [AuthSchemeId] and typed properties. AuthSchemeOption represents a candidate diff --git a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/Identity.kt b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/Identity.kt index 43bc7180a..7211c0119 100644 --- a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/Identity.kt +++ b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/Identity.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.identity +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.Attributes /** * Uniquely-distinguishing properties which identify an actor diff --git a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityAttributes.kt b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityAttributes.kt index 550c3185c..80f19f59c 100644 --- a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityAttributes.kt +++ b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityAttributes.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.identity -import aws.smithy.kotlin.runtime.util.AttributeKey +import aws.smithy.kotlin.runtime.collections.AttributeKey /** * Common [Identity] attribute keys diff --git a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProvider.kt b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProvider.kt index 95eb61d4a..8ea1b0908 100644 --- a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProvider.kt +++ b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProvider.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.identity +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.io.Closeable -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes /** * Resolves identities for a service client diff --git a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProviderChain.kt b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProviderChain.kt index e99f87605..63593510a 100644 --- a/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProviderChain.kt +++ b/runtime/auth/identity-api/common/src/aws/smithy/kotlin/runtime/identity/IdentityProviderChain.kt @@ -6,10 +6,10 @@ package aws.smithy.kotlin.runtime.identity import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.io.Closeable import aws.smithy.kotlin.runtime.telemetry.logging.logger import aws.smithy.kotlin.runtime.telemetry.trace.withSpan -import aws.smithy.kotlin.runtime.util.Attributes // TODO - support caching the provider that actually resolved credentials such that future calls don't involve going through the full chain diff --git a/runtime/auth/identity-api/common/test/aws/smithy/kotlin/runtime/identity/IdentityProviderChainTest.kt b/runtime/auth/identity-api/common/test/aws/smithy/kotlin/runtime/identity/IdentityProviderChainTest.kt index 3e5cd1234..6cec13509 100644 --- a/runtime/auth/identity-api/common/test/aws/smithy/kotlin/runtime/identity/IdentityProviderChainTest.kt +++ b/runtime/auth/identity-api/common/test/aws/smithy/kotlin/runtime/identity/IdentityProviderChainTest.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.identity +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes import io.kotest.matchers.string.shouldContain import kotlinx.coroutines.test.runTest import kotlin.test.Test diff --git a/runtime/observability/telemetry-api/api/telemetry-api.api b/runtime/observability/telemetry-api/api/telemetry-api.api index 95c1065a7..67b65768e 100644 --- a/runtime/observability/telemetry-api/api/telemetry-api.api +++ b/runtime/observability/telemetry-api/api/telemetry-api.api @@ -118,11 +118,11 @@ public final class aws/smithy/kotlin/runtime/telemetry/logging/LoggingContextEle } public abstract interface class aws/smithy/kotlin/runtime/telemetry/metrics/AsyncMeasurement { - public abstract fun record (Ljava/lang/Number;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V + public abstract fun record (Ljava/lang/Number;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V } public final class aws/smithy/kotlin/runtime/telemetry/metrics/AsyncMeasurement$DefaultImpls { - public static synthetic fun record$default (Laws/smithy/kotlin/runtime/telemetry/metrics/AsyncMeasurement;Ljava/lang/Number;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V + public static synthetic fun record$default (Laws/smithy/kotlin/runtime/telemetry/metrics/AsyncMeasurement;Ljava/lang/Number;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V } public abstract interface class aws/smithy/kotlin/runtime/telemetry/metrics/AsyncMeasurementHandle { @@ -130,16 +130,16 @@ public abstract interface class aws/smithy/kotlin/runtime/telemetry/metrics/Asyn } public abstract interface class aws/smithy/kotlin/runtime/telemetry/metrics/Histogram { - public abstract fun record (Ljava/lang/Number;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V + public abstract fun record (Ljava/lang/Number;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V } public final class aws/smithy/kotlin/runtime/telemetry/metrics/Histogram$DefaultImpls { - public static synthetic fun record$default (Laws/smithy/kotlin/runtime/telemetry/metrics/Histogram;Ljava/lang/Number;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V + public static synthetic fun record$default (Laws/smithy/kotlin/runtime/telemetry/metrics/Histogram;Ljava/lang/Number;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V } public final class aws/smithy/kotlin/runtime/telemetry/metrics/HistogramKt { - public static final fun measureSeconds (Laws/smithy/kotlin/runtime/telemetry/metrics/Histogram;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; - public static synthetic fun measureSeconds$default (Laws/smithy/kotlin/runtime/telemetry/metrics/Histogram;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun measureSeconds (Laws/smithy/kotlin/runtime/telemetry/metrics/Histogram;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public static synthetic fun measureSeconds$default (Laws/smithy/kotlin/runtime/telemetry/metrics/Histogram;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Ljava/lang/Object; } public abstract interface class aws/smithy/kotlin/runtime/telemetry/metrics/Meter { @@ -172,19 +172,19 @@ public final class aws/smithy/kotlin/runtime/telemetry/metrics/MeterProvider$Com } public abstract interface class aws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter { - public abstract fun add (JLaws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V + public abstract fun add (JLaws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V } public final class aws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter$DefaultImpls { - public static synthetic fun add$default (Laws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter;JLaws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V + public static synthetic fun add$default (Laws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter;JLaws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V } public abstract interface class aws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter { - public abstract fun add (JLaws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V + public abstract fun add (JLaws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;)V } public final class aws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter$DefaultImpls { - public static synthetic fun add$default (Laws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter;JLaws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V + public static synthetic fun add$default (Laws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter;JLaws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)V } public final class aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExtKt { @@ -222,15 +222,15 @@ public final class aws/smithy/kotlin/runtime/telemetry/trace/SpanStatus : java/l public abstract interface class aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan : aws/smithy/kotlin/runtime/telemetry/context/Scope { public abstract fun close ()V - public abstract fun emitEvent (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;)V + public abstract fun emitEvent (Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;)V public abstract fun getSpanContext ()Laws/smithy/kotlin/runtime/telemetry/trace/SpanContext; - public abstract fun mergeAttributes (Laws/smithy/kotlin/runtime/util/Attributes;)V - public abstract fun set (Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/Object;)V + public abstract fun mergeAttributes (Laws/smithy/kotlin/runtime/collections/Attributes;)V + public abstract fun set (Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/Object;)V public abstract fun setStatus (Laws/smithy/kotlin/runtime/telemetry/trace/SpanStatus;)V } public final class aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan$DefaultImpls { - public static synthetic fun emitEvent$default (Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)V + public static synthetic fun emitEvent$default (Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;ILjava/lang/Object;)V } public final class aws/smithy/kotlin/runtime/telemetry/trace/TraceSpanContext$Key : kotlin/coroutines/CoroutineContext$Key { @@ -242,11 +242,11 @@ public final class aws/smithy/kotlin/runtime/telemetry/trace/TraceSpanExtKt { } public abstract interface class aws/smithy/kotlin/runtime/telemetry/trace/Tracer { - public abstract fun createSpan (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/trace/SpanKind;Laws/smithy/kotlin/runtime/telemetry/context/Context;)Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan; + public abstract fun createSpan (Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/trace/SpanKind;Laws/smithy/kotlin/runtime/telemetry/context/Context;)Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan; } public final class aws/smithy/kotlin/runtime/telemetry/trace/Tracer$DefaultImpls { - public static synthetic fun createSpan$default (Laws/smithy/kotlin/runtime/telemetry/trace/Tracer;Ljava/lang/String;Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/telemetry/trace/SpanKind;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan; + public static synthetic fun createSpan$default (Laws/smithy/kotlin/runtime/telemetry/trace/Tracer;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/telemetry/trace/SpanKind;Laws/smithy/kotlin/runtime/telemetry/context/Context;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/telemetry/trace/TraceSpan; } public abstract interface class aws/smithy/kotlin/runtime/telemetry/trace/TracerProvider { diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Gauge.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Gauge.kt index 54ae5da19..d24237755 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Gauge.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Gauge.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.telemetry.metrics +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.context.Context -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes /** * Callback parameter passed to record a gauge value diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Histogram.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Histogram.kt index 529ca38fc..1500dc80e 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Histogram.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/Histogram.kt @@ -6,9 +6,9 @@ package aws.smithy.kotlin.runtime.telemetry.metrics import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.context.Context -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes import kotlin.time.Duration import kotlin.time.measureTimedValue diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter.kt index bf714f24c..776e4c58c 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/MonotonicCounter.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.telemetry.metrics +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.context.Context -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes public interface MonotonicCounter { /** diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/NoOpMeterProvider.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/NoOpMeterProvider.kt index bc89ea24d..4f337a3ce 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/NoOpMeterProvider.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/NoOpMeterProvider.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.telemetry.metrics +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.telemetry.context.Context -import aws.smithy.kotlin.runtime.util.Attributes internal object NoOpMeterProvider : MeterProvider { override fun getOrCreateMeter(scope: String): Meter = NoOpMeter diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter.kt index 74f951181..7050f9423 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/metrics/UpDownCounter.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.telemetry.metrics +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.context.Context -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes public interface UpDownCounter { /** diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt index 8e04e1ee1..cb6795fe9 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt @@ -7,12 +7,12 @@ package aws.smithy.kotlin.runtime.telemetry.trace import aws.smithy.kotlin.runtime.ExperimentalApi import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.TelemetryProviderContext import aws.smithy.kotlin.runtime.telemetry.context.TelemetryContextElement import aws.smithy.kotlin.runtime.telemetry.context.telemetryContext import aws.smithy.kotlin.runtime.telemetry.telemetryProvider -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext import kotlin.coroutines.AbstractCoroutineContextElement diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/NoOpTracerProvider.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/NoOpTracerProvider.kt index bf62547cc..af607090d 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/NoOpTracerProvider.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/NoOpTracerProvider.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.telemetry.trace +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.telemetry.context.Context -import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.Attributes internal object NoOpTracerProvider : TracerProvider { override fun getOrCreateTracer(scope: String): Tracer = NoOpTracer diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt index c08d5e64e..2dd9d7f79 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpan.kt @@ -5,10 +5,10 @@ package aws.smithy.kotlin.runtime.telemetry.trace +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.context.Scope -import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes /** * Represents a single operation/task within a trace. Each trace contains a root span and diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpanExt.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpanExt.kt index c31d617a7..18c17f272 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpanExt.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/TraceSpanExt.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.telemetry.trace -import aws.smithy.kotlin.runtime.util.AttributeKey +import aws.smithy.kotlin.runtime.collections.AttributeKey /** * Set common error attributes from an exception diff --git a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/Tracer.kt b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/Tracer.kt index 99a7dcf38..d44ae7456 100644 --- a/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/Tracer.kt +++ b/runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/Tracer.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.telemetry.trace +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.telemetry.context.Context -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes /** * Entry point for creating [TraceSpan] instances. diff --git a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/AttributeUtils.kt b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/AttributeUtils.kt index 9ec09f16b..dd2d9ac76 100644 --- a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/AttributeUtils.kt +++ b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/AttributeUtils.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.telemetry.otel -import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.get +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.get import io.opentelemetry.api.common.AttributeKey as OtelAttributeKey import io.opentelemetry.api.common.Attributes as OtelAttributes diff --git a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelMeterProvider.kt b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelMeterProvider.kt index 48ef08764..71d29b3dc 100644 --- a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelMeterProvider.kt +++ b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelMeterProvider.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.telemetry.otel +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.telemetry.context.Context import aws.smithy.kotlin.runtime.telemetry.metrics.* -import aws.smithy.kotlin.runtime.util.Attributes import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.metrics.ObservableDoubleMeasurement import io.opentelemetry.api.metrics.ObservableLongMeasurement diff --git a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt index e7299b37e..cf6eb39df 100644 --- a/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt +++ b/runtime/observability/telemetry-provider-otel/jvm/src/aws/smithy/kotlin/runtime/telemetry/otel/OtelTracerProvider.kt @@ -5,10 +5,10 @@ package aws.smithy.kotlin.runtime.telemetry.otel +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.telemetry.context.Context import aws.smithy.kotlin.runtime.telemetry.trace.* -import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.Attributes import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.trace.Span as OtelSpan import io.opentelemetry.api.trace.SpanContext as OtelSpanContext diff --git a/runtime/observability/telemetry-provider-otel/jvm/test/aws/smithy/kotlin/runtime/telemetry/otel/AttributeTests.kt b/runtime/observability/telemetry-provider-otel/jvm/test/aws/smithy/kotlin/runtime/telemetry/otel/AttributeTests.kt index 6694717f4..49946da48 100644 --- a/runtime/observability/telemetry-provider-otel/jvm/test/aws/smithy/kotlin/runtime/telemetry/otel/AttributeTests.kt +++ b/runtime/observability/telemetry-provider-otel/jvm/test/aws/smithy/kotlin/runtime/telemetry/otel/AttributeTests.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.telemetry.otel -import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.attributesOf +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.attributesOf import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs diff --git a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt index c3f3311b8..c98be54d0 100644 --- a/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt +++ b/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigning.kt @@ -7,12 +7,12 @@ package aws.smithy.kotlin.runtime.awsprotocol.eventstream import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.auth.awssigning.* +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.io.SdkBuffer import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.text.encoding.decodeHexBytes import aws.smithy.kotlin.runtime.time.Clock import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt b/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt index 01e46fc95..b67257118 100644 --- a/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt +++ b/runtime/protocol/aws-event-stream/common/test/aws/smithy/kotlin/runtime/awsprotocol/eventstream/EventStreamSigningTest.kt @@ -8,13 +8,13 @@ package aws.smithy.kotlin.runtime.awsprotocol.eventstream import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.auth.awssigning.* +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.hashing.sha256 import aws.smithy.kotlin.runtime.io.SdkBuffer import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.text.encoding.encodeToHex import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.ManualClock -import aws.smithy.kotlin.runtime.util.Attributes import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList diff --git a/runtime/protocol/aws-json-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocol.kt b/runtime/protocol/aws-json-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocol.kt index 59ade8891..1cdcd28ed 100644 --- a/runtime/protocol/aws-json-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocol.kt +++ b/runtime/protocol/aws-json-protocols/common/src/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocol.kt @@ -6,10 +6,10 @@ package aws.smithy.kotlin.runtime.awsprotocol.json import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.client.SdkClientOption +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.operation.ModifyRequestMiddleware import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest -import aws.smithy.kotlin.runtime.util.get /** * Http feature that handles AWS JSON protocol behaviors, see: diff --git a/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt b/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt index abd811e6f..17bdcab89 100644 --- a/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt +++ b/runtime/protocol/aws-json-protocols/common/test/aws/smithy/kotlin/runtime/awsprotocol/json/AwsJsonProtocolTest.kt @@ -5,13 +5,13 @@ package aws.smithy.kotlin.runtime.awsprotocol.json +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.operation.* import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.httptest.TestEngine import aws.smithy.kotlin.runtime.operation.ExecutionContext -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ClockSkewInterceptor.kt b/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ClockSkewInterceptor.kt index 39f7391fd..2847e7550 100644 --- a/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ClockSkewInterceptor.kt +++ b/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ClockSkewInterceptor.kt @@ -9,6 +9,7 @@ import aws.smithy.kotlin.runtime.SdkBaseException import aws.smithy.kotlin.runtime.ServiceErrorMetadata import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext import aws.smithy.kotlin.runtime.http.request.HttpRequest @@ -17,7 +18,6 @@ import aws.smithy.kotlin.runtime.http.response.header import aws.smithy.kotlin.runtime.telemetry.logging.logger import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.until -import aws.smithy.kotlin.runtime.util.get import kotlinx.atomicfu.* import kotlin.coroutines.coroutineContext import kotlin.time.Duration diff --git a/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ProtocolErrors.kt b/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ProtocolErrors.kt index 288158598..e3293366e 100644 --- a/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ProtocolErrors.kt +++ b/runtime/protocol/aws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ProtocolErrors.kt @@ -8,8 +8,8 @@ package aws.smithy.kotlin.runtime.awsprotocol import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.ServiceErrorMetadata import aws.smithy.kotlin.runtime.ServiceException +import aws.smithy.kotlin.runtime.collections.setIfValueNotNull import aws.smithy.kotlin.runtime.http.response.HttpResponse -import aws.smithy.kotlin.runtime.util.setIfValueNotNull /** * Common error response details diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptor.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptor.kt index 6c8f4bfed..718b30017 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptor.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptor.kt @@ -4,9 +4,9 @@ */ package aws.smithy.kotlin.runtime.http.engine.okhttp +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.attributesOf import aws.smithy.kotlin.runtime.telemetry.metrics.MonotonicCounter -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.attributesOf import okhttp3.* import okio.* diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt index 15cb9bad3..d65d245be 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpUtils.kt @@ -134,7 +134,10 @@ internal class OkHttpProxyAuthenticator( for (challenge in response.challenges()) { if (challenge.scheme.lowercase() == "okhttp-preemptive" || challenge.scheme == "Basic") { return response.request.newBuilder() - .header("Proxy-Authorization", userInfo.toBasicCredentials()) + .header( + "Proxy-Authorization", + Credentials.basic(userInfo.userName.decoded, userInfo.password.decoded), + ) .build() } } @@ -143,8 +146,6 @@ internal class OkHttpProxyAuthenticator( } } -private fun UserInfo.toBasicCredentials() = Credentials.basic(userName.decoded, password.decoded) - internal class OkHttpDns( private val hr: HostResolver, ) : Dns { diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptorTest.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptorTest.kt index 53f2e9893..578486c99 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptorTest.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/test/aws/smithy/kotlin/runtime/http/engine/okhttp/MetricsInterceptorTest.kt @@ -5,10 +5,10 @@ package aws.smithy.kotlin.runtime.http.engine.okhttp import aws.smithy.kotlin.runtime.ExperimentalApi +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.http.engine.internal.HttpClientMetrics import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.telemetry.otel.OpenTelemetryProvider -import aws.smithy.kotlin.runtime.util.emptyAttributes import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.sdk.metrics.data.MetricData import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EngineAttributes.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EngineAttributes.kt index 2d2fe6585..b8eb7ea18 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EngineAttributes.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/EngineAttributes.kt @@ -6,7 +6,7 @@ package aws.smithy.kotlin.runtime.http.engine import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.util.AttributeKey +import aws.smithy.kotlin.runtime.collections.AttributeKey import kotlin.time.Duration /** diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/internal/HttpClientMetrics.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/internal/HttpClientMetrics.kt index f518ba1c2..a193a81dc 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/internal/HttpClientMetrics.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/internal/HttpClientMetrics.kt @@ -6,13 +6,13 @@ package aws.smithy.kotlin.runtime.http.engine.internal import aws.smithy.kotlin.runtime.ExperimentalApi import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.attributesOf import aws.smithy.kotlin.runtime.io.Closeable import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider import aws.smithy.kotlin.runtime.telemetry.metrics.DoubleHistogram import aws.smithy.kotlin.runtime.telemetry.metrics.LongAsyncMeasurement import aws.smithy.kotlin.runtime.telemetry.metrics.MonotonicCounter -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.attributesOf import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt index 8198209f8..449c52fe3 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptor.kt @@ -9,6 +9,7 @@ import aws.smithy.kotlin.runtime.ClientException import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.client.ProtocolResponseInterceptorContext import aws.smithy.kotlin.runtime.client.RequestInterceptorContext +import aws.smithy.kotlin.runtime.collections.AttributeKey import aws.smithy.kotlin.runtime.hashing.toHashFunction import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.request.HttpRequest @@ -19,7 +20,6 @@ import aws.smithy.kotlin.runtime.http.toHttpBody import aws.smithy.kotlin.runtime.io.* import aws.smithy.kotlin.runtime.telemetry.logging.logger import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String -import aws.smithy.kotlin.runtime.util.AttributeKey import kotlin.coroutines.coroutineContext // The priority to validate response checksums, if multiple are present diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/OperationTelemetryInterceptor.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/OperationTelemetryInterceptor.kt index 2717fce67..ab33776a3 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/OperationTelemetryInterceptor.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/interceptors/OperationTelemetryInterceptor.kt @@ -9,12 +9,15 @@ import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext import aws.smithy.kotlin.runtime.client.ProtocolResponseInterceptorContext import aws.smithy.kotlin.runtime.client.RequestInterceptorContext import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext +import aws.smithy.kotlin.runtime.collections.attributesOf +import aws.smithy.kotlin.runtime.collections.merge +import aws.smithy.kotlin.runtime.collections.mutableAttributesOf +import aws.smithy.kotlin.runtime.collections.takeOrNull import aws.smithy.kotlin.runtime.http.engine.EngineAttributes import aws.smithy.kotlin.runtime.http.operation.OperationMetrics import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.telemetry.metrics.recordSeconds -import aws.smithy.kotlin.runtime.util.* import kotlin.time.TimeMark import kotlin.time.TimeSource diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpOperationContext.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpOperationContext.kt index a3ccb60c4..28ad9f770 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpOperationContext.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/HttpOperationContext.kt @@ -6,10 +6,12 @@ package aws.smithy.kotlin.runtime.http.operation import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.http.HttpCall import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.* import kotlin.time.Duration /** diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationTelemetry.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationTelemetry.kt index 61af5a87c..3f7691ef8 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationTelemetry.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/OperationTelemetry.kt @@ -8,13 +8,13 @@ package aws.smithy.kotlin.runtime.http.operation import aws.smithy.kotlin.runtime.ExperimentalApi import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.client.* +import aws.smithy.kotlin.runtime.collections.* import aws.smithy.kotlin.runtime.http.interceptors.OperationTelemetryInterceptor import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider import aws.smithy.kotlin.runtime.telemetry.TelemetryProviderContext import aws.smithy.kotlin.runtime.telemetry.logging.LoggingContextElement import aws.smithy.kotlin.runtime.telemetry.trace.SpanKind import aws.smithy.kotlin.runtime.telemetry.trace.TraceSpan -import aws.smithy.kotlin.runtime.util.* import kotlin.coroutines.CoroutineContext /** diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt index 389dce03f..19523a83f 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkHttpOperation.kt @@ -7,12 +7,13 @@ package aws.smithy.kotlin.runtime.http.operation import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.client.SdkClientOption +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.HttpHandler import aws.smithy.kotlin.runtime.http.complete import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.telemetry.trace.withSpan -import aws.smithy.kotlin.runtime.util.* +import aws.smithy.kotlin.runtime.util.Uuid import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.job import kotlin.reflect.KClass diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt index dc8cec905..a17ebe437 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/operation/SdkOperationExecution.kt @@ -9,6 +9,9 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.client.LogMode import aws.smithy.kotlin.runtime.client.endpoints.authOptions import aws.smithy.kotlin.runtime.client.logMode +import aws.smithy.kotlin.runtime.collections.attributesOf +import aws.smithy.kotlin.runtime.collections.emptyAttributes +import aws.smithy.kotlin.runtime.collections.merge import aws.smithy.kotlin.runtime.http.HttpCall import aws.smithy.kotlin.runtime.http.HttpHandler import aws.smithy.kotlin.runtime.http.auth.SignHttpRequest @@ -32,9 +35,6 @@ import aws.smithy.kotlin.runtime.telemetry.logging.debug import aws.smithy.kotlin.runtime.telemetry.logging.logger import aws.smithy.kotlin.runtime.telemetry.logging.trace import aws.smithy.kotlin.runtime.telemetry.metrics.measureSeconds -import aws.smithy.kotlin.runtime.util.attributesOf -import aws.smithy.kotlin.runtime.util.emptyAttributes -import aws.smithy.kotlin.runtime.util.merge import kotlin.coroutines.coroutineContext import aws.smithy.kotlin.runtime.io.middleware.decorate as decorateHandler diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/DiscoveredEndpointErrorInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/DiscoveredEndpointErrorInterceptorTest.kt index 2a674c1a5..8c2530a1f 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/DiscoveredEndpointErrorInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/DiscoveredEndpointErrorInterceptorTest.kt @@ -9,10 +9,10 @@ import aws.smithy.kotlin.runtime.ServiceException import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext import aws.smithy.kotlin.runtime.client.SdkClientOption import aws.smithy.kotlin.runtime.client.operationName +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.response.HttpResponse import aws.smithy.kotlin.runtime.operation.ExecutionContext -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.runTest import kotlin.test.* diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt index c7064451a..e657b3a98 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsRequestInterceptorTest.kt @@ -6,6 +6,7 @@ package aws.smithy.kotlin.runtime.http.interceptors import aws.smithy.kotlin.runtime.ClientException +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.hashing.toHashFunction import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext @@ -16,7 +17,6 @@ import aws.smithy.kotlin.runtime.http.request.headers import aws.smithy.kotlin.runtime.httptest.TestEngine import aws.smithy.kotlin.runtime.io.* import aws.smithy.kotlin.runtime.text.encoding.encodeBase64String -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest import kotlin.test.* diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt index 1732e8d73..521db6cd9 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/FlexibleChecksumsResponseInterceptorTest.kt @@ -5,6 +5,7 @@ package aws.smithy.kotlin.runtime.http.interceptors +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.* import aws.smithy.kotlin.runtime.http.HttpCall import aws.smithy.kotlin.runtime.http.interceptors.FlexibleChecksumsResponseInterceptor.Companion.ChecksumHeaderValidated @@ -16,7 +17,6 @@ import aws.smithy.kotlin.runtime.io.SdkSource import aws.smithy.kotlin.runtime.io.source import aws.smithy.kotlin.runtime.operation.ExecutionContext import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.runTest import kotlin.test.* diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptorTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptorTest.kt index 178a93b7e..109d2e3e8 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptorTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/interceptors/Md5ChecksumInterceptorTest.kt @@ -5,6 +5,7 @@ package aws.smithy.kotlin.runtime.http.interceptors +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.SdkHttpClient import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext @@ -13,7 +14,6 @@ import aws.smithy.kotlin.runtime.http.operation.roundTrip import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.httptest.TestEngine import aws.smithy.kotlin.runtime.io.SdkByteReadChannel -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/MutateHeadersTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/MutateHeadersTest.kt index 55c9be90f..f97a98271 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/MutateHeadersTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/MutateHeadersTest.kt @@ -5,6 +5,7 @@ package aws.smithy.kotlin.runtime.http.middleware +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.SdkHttpClient import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext import aws.smithy.kotlin.runtime.http.operation.newTestOperation @@ -12,7 +13,6 @@ import aws.smithy.kotlin.runtime.http.operation.roundTrip import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder import aws.smithy.kotlin.runtime.http.request.headers import aws.smithy.kotlin.runtime.httptest.TestEngine -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/RetryMiddlewareTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/RetryMiddlewareTest.kt index f9f34c671..d80c48ab7 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/RetryMiddlewareTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/middleware/RetryMiddlewareTest.kt @@ -5,6 +5,7 @@ package aws.smithy.kotlin.runtime.http.middleware +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.SdkHttpClient import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext import aws.smithy.kotlin.runtime.http.operation.newTestOperation @@ -15,7 +16,6 @@ import aws.smithy.kotlin.runtime.retries.StandardRetryStrategy import aws.smithy.kotlin.runtime.retries.policy.RetryDirective import aws.smithy.kotlin.runtime.retries.policy.RetryErrorType import aws.smithy.kotlin.runtime.retries.policy.RetryPolicy -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/AuthHandlerTest.kt b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/AuthHandlerTest.kt index 37d47fdff..891c07e94 100644 --- a/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/AuthHandlerTest.kt +++ b/runtime/protocol/http-client/common/test/aws/smithy/kotlin/runtime/http/operation/AuthHandlerTest.kt @@ -8,6 +8,10 @@ package aws.smithy.kotlin.runtime.http.operation import aws.smithy.kotlin.runtime.auth.AuthOption import aws.smithy.kotlin.runtime.auth.AuthSchemeId import aws.smithy.kotlin.runtime.client.endpoints.Endpoint +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.attributesOf +import aws.smithy.kotlin.runtime.collections.get import aws.smithy.kotlin.runtime.http.auth.* import aws.smithy.kotlin.runtime.http.interceptors.InterceptorExecutor import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder @@ -19,10 +23,6 @@ import aws.smithy.kotlin.runtime.io.Handler import aws.smithy.kotlin.runtime.net.Host import aws.smithy.kotlin.runtime.net.Scheme import aws.smithy.kotlin.runtime.operation.ExecutionContext -import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.attributesOf -import aws.smithy.kotlin.runtime.util.get import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index c2f4cdb35..f6af9b24a 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -13,8 +13,8 @@ public class aws/smithy/kotlin/runtime/ErrorMetadata { } public final class aws/smithy/kotlin/runtime/ErrorMetadata$Companion { - public final fun getRetryable ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getThrottlingError ()Laws/smithy/kotlin/runtime/util/AttributeKey; + public final fun getRetryable ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getThrottlingError ()Laws/smithy/kotlin/runtime/collections/AttributeKey; } public abstract interface annotation class aws/smithy/kotlin/runtime/ExperimentalApi : java/lang/annotation/Annotation { @@ -45,11 +45,11 @@ public class aws/smithy/kotlin/runtime/ServiceErrorMetadata : aws/smithy/kotlin/ } public final class aws/smithy/kotlin/runtime/ServiceErrorMetadata$Companion { - public final fun getErrorCode ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getErrorMessage ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getErrorType ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getProtocolResponse ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getRequestId ()Laws/smithy/kotlin/runtime/util/AttributeKey; + public final fun getErrorCode ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getErrorMessage ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getErrorType ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getProtocolResponse ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getRequestId ()Laws/smithy/kotlin/runtime/collections/AttributeKey; } public class aws/smithy/kotlin/runtime/ServiceException : aws/smithy/kotlin/runtime/SdkBaseException { @@ -70,6 +70,47 @@ public final class aws/smithy/kotlin/runtime/ServiceException$ErrorType : java/l public static fun values ()[Laws/smithy/kotlin/runtime/ServiceException$ErrorType; } +public final class aws/smithy/kotlin/runtime/collections/AttributeKey { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Laws/smithy/kotlin/runtime/collections/AttributeKey; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/String;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/collections/AttributeKey; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class aws/smithy/kotlin/runtime/collections/Attributes { + public abstract fun contains (Laws/smithy/kotlin/runtime/collections/AttributeKey;)Z + public abstract fun getKeys ()Ljava/util/Set; + public abstract fun getOrNull (Laws/smithy/kotlin/runtime/collections/AttributeKey;)Ljava/lang/Object; + public abstract fun isEmpty ()Z +} + +public final class aws/smithy/kotlin/runtime/collections/AttributesBuilder { + public fun ()V + public final fun getAttributes ()Laws/smithy/kotlin/runtime/collections/MutableAttributes; + public final fun to (Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/Object;)V + public final fun to (Ljava/lang/String;Ljava/lang/Object;)V +} + +public final class aws/smithy/kotlin/runtime/collections/AttributesKt { + public static final fun attributesOf (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/collections/Attributes; + public static final fun emptyAttributes ()Laws/smithy/kotlin/runtime/collections/Attributes; + public static final fun get (Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/collections/AttributeKey;)Ljava/lang/Object; + public static final fun isNotEmpty (Laws/smithy/kotlin/runtime/collections/Attributes;)Z + public static final fun merge (Laws/smithy/kotlin/runtime/collections/MutableAttributes;Laws/smithy/kotlin/runtime/collections/Attributes;)V + public static final fun mutableAttributes ()Laws/smithy/kotlin/runtime/collections/MutableAttributes; + public static final fun mutableAttributesOf (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/collections/MutableAttributes; + public static final fun putIfAbsent (Laws/smithy/kotlin/runtime/collections/MutableAttributes;Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/Object;)V + public static final fun putIfAbsentNotNull (Laws/smithy/kotlin/runtime/collections/MutableAttributes;Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/Object;)V + public static final fun setIfValueNotNull (Laws/smithy/kotlin/runtime/collections/MutableAttributes;Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/Object;)V + public static final fun take (Laws/smithy/kotlin/runtime/collections/MutableAttributes;Laws/smithy/kotlin/runtime/collections/AttributeKey;)Ljava/lang/Object; + public static final fun takeOrNull (Laws/smithy/kotlin/runtime/collections/MutableAttributes;Laws/smithy/kotlin/runtime/collections/AttributeKey;)Ljava/lang/Object; + public static final fun toMutableAttributes (Laws/smithy/kotlin/runtime/collections/Attributes;)Laws/smithy/kotlin/runtime/collections/MutableAttributes; +} + public abstract interface class aws/smithy/kotlin/runtime/collections/MultiMap : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { public abstract fun contains (Ljava/lang/Object;Ljava/lang/Object;)Z public abstract fun getEntryValues ()Lkotlin/sequences/Sequence; @@ -85,6 +126,12 @@ public final class aws/smithy/kotlin/runtime/collections/MultiMapKt { public static final fun multiMapOf ([Lkotlin/Pair;)Laws/smithy/kotlin/runtime/collections/MultiMap; } +public abstract interface class aws/smithy/kotlin/runtime/collections/MutableAttributes : aws/smithy/kotlin/runtime/collections/Attributes { + public abstract fun computeIfAbsent (Laws/smithy/kotlin/runtime/collections/AttributeKey;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public abstract fun remove (Laws/smithy/kotlin/runtime/collections/AttributeKey;)V + public abstract fun set (Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/Object;)V +} + public abstract interface class aws/smithy/kotlin/runtime/collections/MutableMultiMap : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { public abstract fun add (Ljava/lang/Object;ILjava/lang/Object;)V public abstract fun add (Ljava/lang/Object;Ljava/lang/Object;)Z @@ -112,6 +159,9 @@ public final class aws/smithy/kotlin/runtime/collections/MutableMultiMapKt { public static final fun mutableMultiMapOf ([Lkotlin/Pair;)Laws/smithy/kotlin/runtime/collections/MutableMultiMap; } +public final class aws/smithy/kotlin/runtime/collections/StackKt { +} + public abstract interface class aws/smithy/kotlin/runtime/collections/ValuesMap { public abstract fun contains (Ljava/lang/String;)Z public abstract fun contains (Ljava/lang/String;Ljava/lang/Object;)Z @@ -736,7 +786,7 @@ public final class aws/smithy/kotlin/runtime/net/url/QueryParameters : aws/smith public final fun getEncodedParameters ()Laws/smithy/kotlin/runtime/collections/MultiMap; public fun getEntries ()Ljava/util/Set; public fun getEntryValues ()Lkotlin/sequences/Sequence; - public final fun getForceQuery ()Z + public final fun getForceQuerySeparator ()Z public fun getKeys ()Ljava/util/Set; public fun getSize ()I public fun getValues ()Ljava/util/Collection; @@ -798,7 +848,7 @@ public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Builder : a public final fun getEncodedParameters ()Laws/smithy/kotlin/runtime/collections/MutableMultiMap; public fun getEntries ()Ljava/util/Set; public fun getEntryValues ()Lkotlin/sequences/Sequence; - public final fun getForceQuery ()Z + public final fun getForceQuerySeparator ()Z public fun getKeys ()Ljava/util/Set; public fun getSize ()I public fun getValues ()Ljava/util/Collection; @@ -822,7 +872,7 @@ public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Builder : a public synthetic fun retainAll (Ljava/lang/Object;Ljava/util/Collection;)Ljava/lang/Boolean; public final fun setDecoded (Ljava/lang/String;)V public final fun setEncoded (Ljava/lang/String;)V - public final fun setForceQuery (Z)V + public final fun setForceQuerySeparator (Z)V public final fun size ()I public fun toMultiMap ()Laws/smithy/kotlin/runtime/collections/MultiMap; public final fun values ()Ljava/util/Collection; @@ -838,6 +888,8 @@ public final class aws/smithy/kotlin/runtime/net/url/QueryParameters$Companion { public final class aws/smithy/kotlin/runtime/net/url/Url { public static final field Companion Laws/smithy/kotlin/runtime/net/url/Url$Companion; public synthetic fun (Laws/smithy/kotlin/runtime/net/Scheme;Laws/smithy/kotlin/runtime/net/Host;ILaws/smithy/kotlin/runtime/net/url/UrlPath;Laws/smithy/kotlin/runtime/net/url/QueryParameters;Laws/smithy/kotlin/runtime/net/url/UserInfo;Laws/smithy/kotlin/runtime/text/encoding/Encodable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/net/url/Url; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/net/url/Url;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/net/url/Url; public fun equals (Ljava/lang/Object;)Z public final fun getFragment ()Laws/smithy/kotlin/runtime/text/encoding/Encodable; public final fun getHost ()Laws/smithy/kotlin/runtime/net/Host; @@ -993,19 +1045,19 @@ public final class aws/smithy/kotlin/runtime/net/url/UserInfo$Companion { public final fun parseEncoded (Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/url/UserInfo; } -public final class aws/smithy/kotlin/runtime/operation/ExecutionContext : aws/smithy/kotlin/runtime/util/MutableAttributes, kotlinx/coroutines/CoroutineScope { +public final class aws/smithy/kotlin/runtime/operation/ExecutionContext : aws/smithy/kotlin/runtime/collections/MutableAttributes, kotlinx/coroutines/CoroutineScope { public static final field Companion Laws/smithy/kotlin/runtime/operation/ExecutionContext$Companion; public fun ()V public synthetic fun (Laws/smithy/kotlin/runtime/operation/ExecutionContext$ExecutionContextBuilder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun computeIfAbsent (Laws/smithy/kotlin/runtime/util/AttributeKey;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; - public fun contains (Laws/smithy/kotlin/runtime/util/AttributeKey;)Z - public final fun getAttributes ()Laws/smithy/kotlin/runtime/util/MutableAttributes; + public fun computeIfAbsent (Laws/smithy/kotlin/runtime/collections/AttributeKey;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public fun contains (Laws/smithy/kotlin/runtime/collections/AttributeKey;)Z + public final fun getAttributes ()Laws/smithy/kotlin/runtime/collections/MutableAttributes; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getKeys ()Ljava/util/Set; - public fun getOrNull (Laws/smithy/kotlin/runtime/util/AttributeKey;)Ljava/lang/Object; + public fun getOrNull (Laws/smithy/kotlin/runtime/collections/AttributeKey;)Ljava/lang/Object; public fun isEmpty ()Z - public fun remove (Laws/smithy/kotlin/runtime/util/AttributeKey;)V - public fun set (Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/Object;)V + public fun remove (Laws/smithy/kotlin/runtime/collections/AttributeKey;)V + public fun set (Laws/smithy/kotlin/runtime/collections/AttributeKey;Ljava/lang/Object;)V } public final class aws/smithy/kotlin/runtime/operation/ExecutionContext$Companion { @@ -1015,8 +1067,8 @@ public final class aws/smithy/kotlin/runtime/operation/ExecutionContext$Companio public final class aws/smithy/kotlin/runtime/operation/ExecutionContext$ExecutionContextBuilder { public fun ()V public final fun build ()Laws/smithy/kotlin/runtime/operation/ExecutionContext; - public final fun getAttributes ()Laws/smithy/kotlin/runtime/util/MutableAttributes; - public final fun setAttributes (Laws/smithy/kotlin/runtime/util/MutableAttributes;)V + public final fun getAttributes ()Laws/smithy/kotlin/runtime/collections/MutableAttributes; + public final fun setAttributes (Laws/smithy/kotlin/runtime/collections/MutableAttributes;)V } public final class aws/smithy/kotlin/runtime/retries/AdaptiveRetryStrategy : aws/smithy/kotlin/runtime/retries/StandardRetryStrategy { @@ -1509,47 +1561,6 @@ public final class aws/smithy/kotlin/runtime/time/TimestampFormat : java/lang/En public static fun values ()[Laws/smithy/kotlin/runtime/time/TimestampFormat; } -public final class aws/smithy/kotlin/runtime/util/AttributeKey { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Laws/smithy/kotlin/runtime/util/AttributeKey; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/String;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/util/AttributeKey; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class aws/smithy/kotlin/runtime/util/Attributes { - public abstract fun contains (Laws/smithy/kotlin/runtime/util/AttributeKey;)Z - public abstract fun getKeys ()Ljava/util/Set; - public abstract fun getOrNull (Laws/smithy/kotlin/runtime/util/AttributeKey;)Ljava/lang/Object; - public abstract fun isEmpty ()Z -} - -public final class aws/smithy/kotlin/runtime/util/AttributesBuilder { - public fun ()V - public final fun getAttributes ()Laws/smithy/kotlin/runtime/util/MutableAttributes; - public final fun to (Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/Object;)V - public final fun to (Ljava/lang/String;Ljava/lang/Object;)V -} - -public final class aws/smithy/kotlin/runtime/util/AttributesKt { - public static final fun attributesOf (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/util/Attributes; - public static final fun emptyAttributes ()Laws/smithy/kotlin/runtime/util/Attributes; - public static final fun get (Laws/smithy/kotlin/runtime/util/Attributes;Laws/smithy/kotlin/runtime/util/AttributeKey;)Ljava/lang/Object; - public static final fun isNotEmpty (Laws/smithy/kotlin/runtime/util/Attributes;)Z - public static final fun merge (Laws/smithy/kotlin/runtime/util/MutableAttributes;Laws/smithy/kotlin/runtime/util/Attributes;)V - public static final fun mutableAttributes ()Laws/smithy/kotlin/runtime/util/MutableAttributes; - public static final fun mutableAttributesOf (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/util/MutableAttributes; - public static final fun putIfAbsent (Laws/smithy/kotlin/runtime/util/MutableAttributes;Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/Object;)V - public static final fun putIfAbsentNotNull (Laws/smithy/kotlin/runtime/util/MutableAttributes;Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/Object;)V - public static final fun setIfValueNotNull (Laws/smithy/kotlin/runtime/util/MutableAttributes;Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/Object;)V - public static final fun take (Laws/smithy/kotlin/runtime/util/MutableAttributes;Laws/smithy/kotlin/runtime/util/AttributeKey;)Ljava/lang/Object; - public static final fun takeOrNull (Laws/smithy/kotlin/runtime/util/MutableAttributes;Laws/smithy/kotlin/runtime/util/AttributeKey;)Ljava/lang/Object; - public static final fun toMutableAttributes (Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/util/MutableAttributes; -} - public abstract interface class aws/smithy/kotlin/runtime/util/Buildable { public abstract fun build ()Ljava/lang/Object; } @@ -1588,12 +1599,6 @@ public final class aws/smithy/kotlin/runtime/util/JMESPathKt { public final class aws/smithy/kotlin/runtime/util/LazyAsyncValueKt { } -public abstract interface class aws/smithy/kotlin/runtime/util/MutableAttributes : aws/smithy/kotlin/runtime/util/Attributes { - public abstract fun computeIfAbsent (Laws/smithy/kotlin/runtime/util/AttributeKey;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; - public abstract fun remove (Laws/smithy/kotlin/runtime/util/AttributeKey;)V - public abstract fun set (Laws/smithy/kotlin/runtime/util/AttributeKey;Ljava/lang/Object;)V -} - public final class aws/smithy/kotlin/runtime/util/OperatingSystem { public fun (Laws/smithy/kotlin/runtime/util/OsFamily;Ljava/lang/String;)V public final fun component1 ()Laws/smithy/kotlin/runtime/util/OsFamily; @@ -1641,9 +1646,6 @@ public abstract interface class aws/smithy/kotlin/runtime/util/PropertyProvider public abstract fun getProperty (Ljava/lang/String;)Ljava/lang/String; } -public final class aws/smithy/kotlin/runtime/util/StackKt { -} - public final class aws/smithy/kotlin/runtime/util/TestPlatformProvider$Companion { } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/Exceptions.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/Exceptions.kt index 6632d3156..cee3e49b1 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/Exceptions.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/Exceptions.kt @@ -4,9 +4,9 @@ */ package aws.smithy.kotlin.runtime -import aws.smithy.kotlin.runtime.util.AttributeKey -import aws.smithy.kotlin.runtime.util.MutableAttributes -import aws.smithy.kotlin.runtime.util.mutableAttributes +import aws.smithy.kotlin.runtime.collections.AttributeKey +import aws.smithy.kotlin.runtime.collections.MutableAttributes +import aws.smithy.kotlin.runtime.collections.mutableAttributes /** * Additional metadata about an error diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Attributes.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Attributes.kt similarity index 99% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Attributes.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Attributes.kt index 9276a906b..b2a4ef390 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Attributes.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Attributes.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections /** * Specifies a key for an attribute diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/CaseInsensitiveMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMap.kt similarity index 73% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/CaseInsensitiveMap.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMap.kt index f562c877d..b2546349e 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/CaseInsensitiveMap.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMap.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import aws.smithy.kotlin.runtime.InternalApi @@ -56,24 +56,24 @@ internal class CaseInsensitiveMap() : MutableMap { } override fun remove(key: String): Value? = impl.remove(key.toInsensitive()) -} -private class Entry( - override val key: Key, - override var value: Value, -) : MutableMap.MutableEntry { + private class Entry( + override val key: Key, + override var value: Value, + ) : MutableMap.MutableEntry { - override fun setValue(newValue: Value): Value { - value = newValue - return value - } + override fun setValue(newValue: Value): Value { + value = newValue + return value + } - override fun hashCode(): Int = 17 * 31 + key!!.hashCode() + value!!.hashCode() + override fun hashCode(): Int = 17 * 31 + key!!.hashCode() + value!!.hashCode() - override fun equals(other: Any?): Boolean { - if (other == null || other !is Map.Entry<*, *>) return false - return other.key == key && other.value == value - } + override fun equals(other: Any?): Boolean { + if (other == null || other !is Map.Entry<*, *>) return false + return other.key == key && other.value == value + } - override fun toString(): String = "$key=$value" + override fun toString(): String = "$key=$value" + } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt index cecf62bcc..cc0f6c88a 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Entry.kt @@ -4,4 +4,4 @@ */ package aws.smithy.kotlin.runtime.collections -internal class Entry(override val key: K, override val value: V) : Map.Entry +internal data class Entry(override val key: K, override val value: V) : Map.Entry diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt index 1f2145657..46ff51681 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MultiMap.kt @@ -4,15 +4,38 @@ */ package aws.smithy.kotlin.runtime.collections +/** + * A collection similar to a [Map] except it allows multiple values to be associated with a single key. The associated + * values are not necessarily distinct (e.g., key `foo` may be associated with value `bar` multiple times). + * @param K The type of elements used as keys + * @param V The type of elements used as values + */ public interface MultiMap : Map> { + /** + * Checks if the specified [value] is present for the given [key]. Returns false if [key] is not present or if it + * is not associated with [value]. + */ public fun contains(key: K, value: V): Boolean = get(key)?.contains(value) ?: false - public val entryValues: Sequence> // TODO Make more dynamic, e.g. Set + /** + * Gets a [Sequence] of key-value pairs. A given key will appear multiple times in the sequence if it is associated + * with multiple values. + */ + public val entryValues: Sequence> + /** + * Returns a mutable copy of this multimap. Changes to the returned mutable multimap do not affect this instance. + */ public fun toMutableMultiMap(): MutableMultiMap = SimpleMutableMultiMap(mapValuesTo(mutableMapOf()) { (_, v) -> v.toMutableList() }) } +/** + * Create a new [MultiMap] from the given key-value pairs + * @param K The type of elements used as keys + * @param V The type of elements used as values + * @param pairs The elements to be contained by the new multimap + */ public fun multiMapOf(vararg pairs: Pair): MultiMap = SimpleMultiMap(pairs.groupBy(Pair::first, Pair::second)) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt index f35191ad0..413bad7b3 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/MutableMultiMap.kt @@ -6,32 +6,133 @@ package aws.smithy.kotlin.runtime.collections import kotlin.jvm.JvmName -// TODO can this inherit from MultiMap? +/** + * A mutable collection similar to a [MutableMap] except it allows multiple values to be associated with a single key. + * The associated values are not necessarily distinct (e.g., key `foo` may be associated with value `bar` multiple + * times). + * @param K The type of elements used as keys + * @param V The type of elements used as values + */ public interface MutableMultiMap : MutableMap> { + /** + * Adds an association from the given [key] to the given [value] + * @param key The key to associate + * @param value The value to associate + * @return True because the multimap is always modified as the result of this operation + */ public fun add(key: K, value: V): Boolean + + /** + * Adds an association from the given [key] to the given [value], inserting it at the given [index] in the list of + * values already associated with the [key]. + * @param key The key to associate + * @param index The index at which to insert [value] in the list of associated values + * @param value The value to associate + */ public fun add(key: K, index: Int, value: V) + + /** + * Adds associations from the given [key] to the given [values]. This will _append_ to the existing associations, + * not merge or deduplicate. This operation copies from the given [values]. Later changes to the collection do not + * affect this instance. + * @param key The key to associate + * @param values The values to associate + * @return True because the multimap is always modified as the result of this operation + */ public fun addAll(key: K, values: Collection): Boolean + + /** + * Adds associations from the given [key] to the given [values], inserting them at the given [index] in the list of + * values already associated with the [key]. This operation copies from the given [values]. Later changes to the + * collection do not affect this instance. + * @param key The key to associate + * @param index The index at which to insert [values] in the list of associated values + * @param values The values to associate + * @return True because the multimap is always modified as the result of this operation + */ public fun addAll(key: K, index: Int, values: Collection): Boolean + /** + * Adds all the key-value associations from another map into this one. This will _append_ to the existing + * associations, not merge or deduplicate. This operation copies from the given values lists. Later changes to those + * lists do not affect this collection. + * @param other The other map from which to copy values + */ public fun addAll(other: Map>) { other.entries.forEach { (key, values) -> addAll(key, values) } } + /** + * Checks if the specified [value] is present for the given [key]. Returns false if [key] is not present or if it + * is not associated with [value]. + */ public fun contains(key: K, value: V): Boolean = get(key)?.contains(value) ?: false - public val entryValues: Sequence> // TODO Make more dynamic, e.g. Set + /** + * Gets a [Sequence] of key-value pairs. A given key will appear multiple times in the sequence if it is associated + * with multiple values. This sequence lazily enumerates over keys and values in the multimap and may reflect + * changes which occurred after the iteration began. + */ + public val entryValues: Sequence> + + /** + * Sets an association from the given [key] to the given [value]. This operation replaces any existing associations + * between [key] and other values. + * @param key The key to associate + * @param value The value to associate + * @return The previous values associated with [key] or null if no values were previously associated. + */ public fun put(key: K, value: V): MutableList? = put(key, mutableListOf(value)) + /** + * Removes the association from the given [key] to the given [value]. + * @param key The key to disassociate + * @param value The value to disassociate + * @return True if the given value was previously associated with the given key; otherwise, false. + */ @Suppress("INAPPLICABLE_JVM_NAME") @JvmName("removeElement") public fun remove(key: K, value: V): Boolean + /** + * Removes the association from the given [key] to the value at the given [index] in the list of existing + * associations. + * @param key The key to disassociate + * @param index The index of the value to disassociate + * @return The value that was removed. If the given [key] wasn't associated with any values previously, returns + * null. + */ public fun removeAt(key: K, index: Int): V? + + /** + * Removes the associations from the given [key] to the given [values]. + * @param key The key to disassociate + * @param values The values to disassociate + * @return True if the list of associations was modified; otherwise, false. + */ public fun removeAll(key: K, values: Collection): Boolean? + + /** + * Retains only associations from the given [key] to the given [values]. Any other associations from the given [key] + * to other values are removed. + * @param key The key to disassociate + * @param values The values to retrain + * @param True if the list of associations was modified; otherwise, false. + */ public fun retainAll(key: K, values: Collection): Boolean? + + /** + * Returns a new read-only multimap containing all the key-value associations from this multimap + */ public fun toMultiMap(): MultiMap = SimpleMultiMap(mapValues { (_, v) -> v.toList() }.toMap()) } +/** + * Create a new [MutableMultiMap] from the given key-value pairs. + * @param K The type of elements used as keys + * @param V The type of elements used as values + * @param pairs The elements to be contained by the new multimap + */ public fun mutableMultiMapOf(vararg pairs: Pair): MutableMultiMap = SimpleMutableMultiMap(pairs.groupByTo(mutableMapOf(), Pair::first, Pair::second)) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/ReadThroughCache.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ReadThroughCache.kt similarity index 97% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/ReadThroughCache.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ReadThroughCache.kt index ddcb564f5..529a57d45 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/ReadThroughCache.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ReadThroughCache.kt @@ -2,10 +2,11 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.time.Clock +import aws.smithy.kotlin.runtime.util.ExpiringValue import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlin.time.Duration diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Stack.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Stack.kt similarity index 96% rename from runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Stack.kt rename to runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Stack.kt index 84973674a..c2916c44e 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Stack.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/Stack.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import aws.smithy.kotlin.runtime.InternalApi diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ValuesMap.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ValuesMap.kt index e15e6966a..763fa21ec 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ValuesMap.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/ValuesMap.kt @@ -5,7 +5,6 @@ package aws.smithy.kotlin.runtime.collections import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.util.CaseInsensitiveMap /** * Mapping of [String] to a List of [T] values diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt index c89c5fe36..c555d3d72 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt @@ -21,7 +21,7 @@ public class QueryParameters private constructor( * A flag indicating whether to force inclusion of the `?` query separator even when there are no parameters (e.g., * `http://foo.com?` vs `http://foo.com`). */ - public val forceQuery: Boolean, + public val forceQuerySeparator: Boolean, ) : MultiMap by delegate { public companion object { /** @@ -36,21 +36,21 @@ public class QueryParameters private constructor( */ public operator fun invoke(block: Builder.() -> Unit): QueryParameters = Builder().apply(block).build() - private fun asDecoded(values: Sequence>, forceQuery: Boolean) = - asString(values, forceQuery, Encodable::decoded) + private fun asDecoded(values: Sequence>, forceQuerySeparator: Boolean) = + asString(values, forceQuerySeparator, Encodable::decoded) - private fun asEncoded(values: Sequence>, forceQuery: Boolean) = - asString(values, forceQuery, Encodable::encoded) + private fun asEncoded(values: Sequence>, forceQuerySeparator: Boolean) = + asString(values, forceQuerySeparator, Encodable::encoded) private fun asString( values: Sequence>, - forceQuery: Boolean, + forceQuerySeparator: Boolean, encodableForm: (Encodable) -> String, ) = values .joinToString( separator = "&", - prefix = if (forceQuery || values.any()) "?" else "", + prefix = if (forceQuerySeparator || values.any()) "?" else "", ) { (key, value) -> "${encodableForm(key)}=${encodableForm(value)}" } /** @@ -72,8 +72,11 @@ public class QueryParameters private constructor( * Copy the properties of this [QueryParameters] instance into a new [Builder] object. Any changes to the builder * *will not* affect this instance. */ - public fun toBuilder(): Builder = Builder(delegate.toMutableMultiMap(), forceQuery) + public fun toBuilder(): Builder = Builder(delegate.toMutableMultiMap(), forceQuerySeparator) + /** + * Gets the query parameters as decoded keys/values + */ public val decodedParameters: MultiMap get() = asView( Encodable::decoded, @@ -82,6 +85,9 @@ public class QueryParameters private constructor( PercentEncoding.Query::encodableFromDecoded, ) + /** + * Gets the query parameters as encoded keys/values + */ public val encodedParameters: MultiMap get() = asView( Encodable::encoded, @@ -97,18 +103,18 @@ public class QueryParameters private constructor( other as QueryParameters if (delegate != other.delegate) return false - if (forceQuery != other.forceQuery) return false + if (forceQuerySeparator != other.forceQuerySeparator) return false return true } override fun hashCode(): Int { var result = delegate.hashCode() - result = 31 * result + forceQuery.hashCode() + result = 31 * result + forceQuerySeparator.hashCode() return result } - override fun toString(): String = asEncoded(delegate.entryValues, forceQuery) + override fun toString(): String = asEncoded(delegate.entryValues, forceQuerySeparator) /** * A mutable builder used to construct [QueryParameters] instances @@ -120,7 +126,7 @@ public class QueryParameters private constructor( * A flag indicating whether to force inclusion of the `?` query separator even when there are no parameters * (e.g., `http://foo.com?` vs `http://foo.com`). */ - public var forceQuery: Boolean = false, + public var forceQuerySeparator: Boolean = false, ) : MutableMultiMap by delegate { /** * Initialize an empty [QueryParameters] builder @@ -131,14 +137,14 @@ public class QueryParameters private constructor( * Get or set the query parameters as a **decoded** string. */ public var decoded: String - get() = asDecoded(delegate.entryValues, forceQuery) + get() = asDecoded(delegate.entryValues, forceQuerySeparator) set(value) { parseDecoded(value) } /** * Get or set the query parameters as an **encoded** string. */ public var encoded: String - get() = asEncoded(delegate.entryValues, forceQuery) + get() = asEncoded(delegate.entryValues, forceQuerySeparator) set(value) { parseEncoded(value) } internal fun parse(value: String, encoding: UrlEncoding): Unit = @@ -150,7 +156,7 @@ public class QueryParameters private constructor( private fun parseInto(map: MutableMultiMap, text: String) { clear() - forceQuery = text == "?" + forceQuerySeparator = text == "?" val params = text.removePrefix("?") if (params.isNotEmpty()) { @@ -170,6 +176,10 @@ public class QueryParameters private constructor( } } + /** + * Gets the query parameters as decoded keys/values. Changes to this map are reflected in the + * [encodedParameters] map as well. + */ public val decodedParameters: MutableMultiMap = asView( Encodable::decoded, PercentEncoding.Query::encodableFromDecoded, @@ -177,10 +187,18 @@ public class QueryParameters private constructor( PercentEncoding.Query::encodableFromDecoded, ) + /** + * Applies the given DSL block to the decoded query parameters. Changes to this map are reflected in the + * [encodedParameters] map as well. + */ public fun decodedParameters(block: MutableMultiMap.() -> Unit) { decodedParameters.apply(block) } + /** + * Gets the query parameters as encoded keys/values. Changes to this map are reflected in the + * [decodedParameters] map as well. + */ public val encodedParameters: MutableMultiMap = asView( Encodable::encoded, PercentEncoding.Query::encodableFromEncoded, @@ -188,6 +206,10 @@ public class QueryParameters private constructor( PercentEncoding.Query::encodableFromEncoded, ) + /** + * Applies the given DSL block to the encoded query parameters. Changes to this map are reflected in the + * [decodedParameters] map as well. + */ public fun encodedParameters(block: MutableMultiMap.() -> Unit) { encodedParameters.apply(block) } @@ -196,22 +218,28 @@ public class QueryParameters private constructor( * Build a new [QueryParameters] from the currently-configured builder values * @return A new [QueryParameters] instance */ - public fun build(): QueryParameters = QueryParameters(delegate.toMultiMap(), forceQuery) + public fun build(): QueryParameters = QueryParameters(delegate.toMultiMap(), forceQuerySeparator) + /** + * Copies the state from [other] into this builder. All existing state is overwritten. + */ public fun copyFrom(other: QueryParameters) { clear() other.mapValuesTo(this) { (_, values) -> values.toMutableList() // Copy the mutable list to a new mutable list } - forceQuery = other.forceQuery + forceQuerySeparator = other.forceQuerySeparator } + /** + * Copies the state from [other] into this builder. All existing state is overwritten. + */ public fun copyFrom(other: Builder) { clear() other.mapValuesTo(this) { (_, values) -> values.toMutableList() // Copy the mutable list to a new mutable list } - forceQuery = other.forceQuery + forceQuerySeparator = other.forceQuerySeparator } } } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt index e7195a01a..47c59fe72 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/Url.kt @@ -120,6 +120,17 @@ public class Url private constructor( } private val encoded: String + + /** + * Gets a request-relative path string for this URL which is suitable for use in an HTTP request line. The given + * path will include query parameters and the fragment and will be prepended with a `/` (even for empty paths + * without a trailing slash configured). It will not include the protocol, host, port, or user info. + * + * For example: + * ``` + * /path/to/resource?key=value#fragment + * ``` + */ public val requestRelativePath: String init { @@ -137,6 +148,10 @@ public class Url private constructor( */ public fun toBuilder(): Builder = Builder(this) + /** + * Returns a copy of this URL with the given DSL builder block applied to modify any desired fields. The returned + * instance is disconnected from this instance. + */ public fun copy(block: Builder.() -> Unit = { }): Url = toBuilder().apply(block).build() override fun equals(other: Any?): Boolean { @@ -296,6 +311,9 @@ public class Url private constructor( it.fragment = fragment } + /** + * Copies state from [url] into this builder. All existing state is overwritten. + */ public fun copyFrom(url: Url) { scheme = url.scheme host = url.host @@ -306,6 +324,16 @@ public class Url private constructor( fragment = url.fragment } + /** + * Gets a request-relative path string for this URL which is suitable for use in an HTTP request line. The given + * path will include query parameters and the fragment and will be prepended with a `/` (even for empty paths + * without a trailing slash configured). It will not include the protocol, host, port, or user info. + * + * For example: + * ``` + * /path/to/resource?key=value#fragment + * ``` + */ public val requestRelativePath: String get() = buildString { append(path.encoded) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt index 12061598b..561e6ca50 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UrlPath.kt @@ -114,6 +114,9 @@ public class UrlPath private constructor( get() = asEncoded(segments, trailingSlash) set(value) { parseEncoded(value) } + /** + * Gets the segments of this URL path + */ public val segments: MutableList = path?.segments?.toMutableList() ?: mutableListOf() /** @@ -124,6 +127,10 @@ public class UrlPath private constructor( PercentEncoding.Path::encodableFromDecoded, ) + /** + * Applies the given DSL block to the **decoded** path segments. Any changes to the list will update the + * builder. + */ public fun decodedSegments(block: MutableList.() -> Unit) { decodedSegments.apply(block) } @@ -136,6 +143,10 @@ public class UrlPath private constructor( PercentEncoding.Path::encodableFromEncoded, ) + /** + * Applies the given DSL block to the **encoded** path segments. Any changes to the list will update the + * builder. + */ public fun encodedSegments(block: MutableList.() -> Unit) { encodedSegments.apply(block) } @@ -200,12 +211,18 @@ public class UrlPath private constructor( */ public fun build(): UrlPath = UrlPath(segments.toList(), trailingSlash) + /** + * Copies the state from [other] into this builder. All existing state is overwritten. + */ public fun copyFrom(other: UrlPath) { segments.clear() segments.addAll(other.segments) trailingSlash = other.trailingSlash } + /** + * Copies the state from [other] into this builder. All existing state is overwritten. + */ public fun copyFrom(other: Builder) { segments.clear() segments.addAll(other.segments) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt index 37c2addbf..039561e99 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/UserInfo.kt @@ -45,7 +45,14 @@ public class UserInfo private constructor(public val userName: Encodable, public require(password.isEmpty || userName.isNotEmpty) { "Cannot have a password without a user name" } } + /** + * Indicates whether this [UserInfo] has a blank [userName] and [password] + */ public val isEmpty: Boolean = userName.isEmpty && password.isEmpty + + /** + * Indicates whether this [UserInfo] has a non-blank [userName] or [password] + */ public val isNotEmpty: Boolean = !isEmpty /** @@ -143,11 +150,17 @@ public class UserInfo private constructor(public val userName: Encodable, public */ public fun build(): UserInfo = UserInfo(userName, password) + /** + * Copies the state from [other] into this builder. All existing state is overwritten. + */ public fun copyFrom(other: UserInfo) { userName = other.userName password = other.password } + /** + * Copies the state from [other] into this builder. All existing state is overwritten. + */ public fun copyFrom(other: Builder) { userName = other.userName password = other.password diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/operation/ExecutionContext.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/operation/ExecutionContext.kt index 7f224448c..72b940813 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/operation/ExecutionContext.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/operation/ExecutionContext.kt @@ -4,8 +4,8 @@ */ package aws.smithy.kotlin.runtime.operation -import aws.smithy.kotlin.runtime.util.MutableAttributes -import aws.smithy.kotlin.runtime.util.mutableAttributes +import aws.smithy.kotlin.runtime.collections.MutableAttributes +import aws.smithy.kotlin.runtime.collections.mutableAttributes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlin.coroutines.CoroutineContext diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt index 53de00807..4820c5c3d 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt @@ -4,16 +4,33 @@ */ package aws.smithy.kotlin.runtime.text.encoding +/** + * An immutable encapsulation of data in its original (decoded) format, an encoding, and the data encoded in that + * format. + * @param decoded The decoded data + * @param encoded The encoded data + * @param encoding The encoding used to encode the data + */ public class Encodable internal constructor( public val decoded: String, public val encoded: String, public val encoding: Encoding, ) { public companion object { + /** + * An empty encodable, containing empty decoded/encoded data in no encoding format + */ public val Empty: Encodable = Encodable("", "", Encoding.None) } + /** + * Indicates whether this [Encodable] has an empty [decoded] and [encoded] representation + */ public val isEmpty: Boolean = decoded.isEmpty() && encoded.isEmpty() + + /** + * Indicates whether this [Encodable] has a non-empty [decoded] or [encoded] representation + */ public val isNotEmpty: Boolean = !isEmpty override fun equals(other: Any?): Boolean { @@ -32,6 +49,10 @@ public class Encodable internal constructor( return result } + /** + * Returns a new [Encodable] derived from re-encoding this instance's [decoded] data. This _may_ be different from + * the current instance's [encoded] data if the object was created with a non-canonical encoding. + */ public fun reencode(): Encodable = encoding.encodableFromDecoded(decoded) override fun toString(): String = buildString { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt index 316a619a3..61b53aa21 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encoding.kt @@ -6,6 +6,9 @@ package aws.smithy.kotlin.runtime.text.encoding import aws.smithy.kotlin.runtime.InternalApi +/** + * An algorithm which can convert data between a decoded string and an encoded string + */ @InternalApi public interface Encoding { @InternalApi @@ -17,15 +20,28 @@ public interface Encoding { } } + /** + * The name of this encoding + */ public val name: String + /** + * Given **encoded** data, returns the **decoded* representation + */ public fun decode(encoded: String): String + + /** + * Given **decoded** data, returns the **encoded* representation + */ public fun encode(decoded: String): String + /** + * Given **decoded** data, returns an [Encodable] containing both the decoded and encoded data + */ public fun encodableFromDecoded(decoded: String): Encodable = Encodable(decoded, encode(decoded), this) - public fun encodableFromEncoded(encoded: String): Encodable { - val decoded = decode(encoded) - // val reencoded = encode(decoded) // TODO is this right? - return Encodable(decoded, encoded, this) - } + + /** + * Given **encoded** data, returns an [Encodable] containing both the decoded and encoded data. + */ + public fun encodableFromEncoded(encoded: String): Encodable = Encodable(decode(encoded), encoded, this) } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt index e79393e62..d9474a314 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/PercentEncoding.kt @@ -6,6 +6,14 @@ package aws.smithy.kotlin.runtime.text.encoding import aws.smithy.kotlin.runtime.InternalApi +/** + * An algorithm that percent-encodes string data for use in URLs + * @param name The name of this encoding + * @param validChars The set of characters which are valid _unencoded_ (i.e., in plain text). All other characters will + * be percent-encoded unless they appear in [specialMapping]. + * @param specialMapping A mapping of characters to their special (i.e., non-percent-encoded) form. The characters which + * are keys in this map will not be percent-encoded. + */ @InternalApi public class PercentEncoding( override val name: String, @@ -14,6 +22,7 @@ public class PercentEncoding( ) : Encoding { @InternalApi public companion object { + // These definitions are from RFC 3986 Appendix A, see https://datatracker.ietf.org/doc/html/rfc3986#appendix-A private val ALPHA = (('A'..'Z') + ('a'..'z')).toSet() private val DIGIT = ('0'..'9').toSet() private val UNRESERVED = ALPHA + DIGIT + setOf('-', '.', '_', '~') @@ -26,18 +35,44 @@ public class PercentEncoding( // what MUST be encoded in queries: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html private val VALID_QCHAR = UNRESERVED - // Undocumented formally but crafted to pass Smithy protocol tests, most especially this one: - // https://github.com/smithy-lang/smithy/blob/d457aabb80feb4088caa3ac27d337b84e3ebc43d/smithy-aws-protocol-tests/model/restXml/http-labels.smithy#L42-L59 + // https://smithy.io/2.0/spec/http-bindings.html#httplabel-serialization-rules private val SMITHY_LABEL_CHAR = UNRESERVED private const val UPPER_HEX = "0123456789ABCDEF" + /** + * A [PercentEncoding] instance suitable for encoding host names/addresses + */ public val Host: Encoding = PercentEncoding("host", UNRESERVED + ':') // e.g., for IPv6 zone ID encoding + + /** + * A [PercentEncoding] instance suitable for encoding userinfo + */ public val UserInfo: Encoding = PercentEncoding("user info", VALID_UCHAR) + + /** + * A [PercentEncoding] instance suitable for encoding URL paths + */ public val Path: Encoding = PercentEncoding("path", VALID_PCHAR) + + /** + * A [PercentEncoding] instance suitable for encoding query strings + */ public val Query: Encoding = PercentEncoding("query string", VALID_QCHAR) + + /** + * A [PercentEncoding] instance suitable for encoding URL fragments + */ public val Fragment: Encoding = PercentEncoding("fragment", VALID_FCHAR) + + /** + * A [PercentEncoding] instance suitable for encoding `application/x-www-form-urlencoded` data + */ public val FormUrl: Encoding = PercentEncoding("form URL", VALID_QCHAR, mapOf(' ' to '+')) + + /** + * A [PercentEncoding] instance suitable for encoding values into Smithy labels + */ public val SmithyLabel: Encoding = PercentEncoding("Smithy label", SMITHY_LABEL_CHAR) private fun percentAsciiEncode(char: Char) = buildString { @@ -47,7 +82,7 @@ public class PercentEncoding( append(UPPER_HEX[value and 0x0f]) } - public fun StringBuilder.percentEncode(byte: Byte) { + private fun StringBuilder.percentEncode(byte: Byte) { val value = byte.toInt() and 0xff append('%') append(UPPER_HEX[value shr 4]) diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/AttributesTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/AttributesTest.kt similarity index 98% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/AttributesTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/AttributesTest.kt index cce5de281..c91db10f0 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/AttributesTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/AttributesTest.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import kotlin.test.* diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/CaseInsensitiveMapTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMapTest.kt similarity index 92% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/CaseInsensitiveMapTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMapTest.kt index 2e66c231c..dcf919d52 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/CaseInsensitiveMapTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/CaseInsensitiveMapTest.kt @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/ReadThroughCacheTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/ReadThroughCacheTest.kt similarity index 95% rename from runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/ReadThroughCacheTest.kt rename to runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/ReadThroughCacheTest.kt index ed4cdbe0e..4d85ece15 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/util/ReadThroughCacheTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/ReadThroughCacheTest.kt @@ -2,9 +2,10 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.smithy.kotlin.runtime.util +package aws.smithy.kotlin.runtime.collections import aws.smithy.kotlin.runtime.time.ManualClock +import aws.smithy.kotlin.runtime.util.ExpiringValue import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt index 14a49a4db..5d1d2a9b4 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt @@ -11,7 +11,7 @@ class QueryParametersTest { @Test fun testParse() { val actual = QueryParameters.parseEncoded("?") - val expected = QueryParameters { forceQuery = true } + val expected = QueryParameters { forceQuerySeparator = true } assertEquals(expected, actual) } } diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt index cc57fc2bd..3e4828773 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt @@ -173,7 +173,7 @@ class UrlParsingTest { ).forEach { url -> val parsed = Url.parse(url).parameters assertEquals(0, parsed.size) - assertFalse(parsed.forceQuery, "Expected forceQuery=false for $url") + assertFalse(parsed.forceQuerySeparator, "Expected forceQuery=false for $url") } } @@ -227,7 +227,7 @@ class UrlParsingTest { ).forEach { url -> val parsed = Url.parse(url).parameters assertEquals(0, parsed.size) - assertTrue(parsed.forceQuery, "Expected forceQuery=true for $url") + assertTrue(parsed.forceQuerySeparator, "Expected forceQuery=true for $url") } } diff --git a/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonEncoder.kt b/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonEncoder.kt index 7e48f4133..71d2f86d6 100644 --- a/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonEncoder.kt +++ b/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonEncoder.kt @@ -5,9 +5,9 @@ package aws.smithy.kotlin.runtime.serde.json +import aws.smithy.kotlin.runtime.collections.* import aws.smithy.kotlin.runtime.content.BigDecimal import aws.smithy.kotlin.runtime.content.BigInteger -import aws.smithy.kotlin.runtime.util.* // character code points private const val CP_QUOTATION = 0x22 diff --git a/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonLexer.kt b/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonLexer.kt index 522bf49c2..a8e956849 100644 --- a/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonLexer.kt +++ b/runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonLexer.kt @@ -5,8 +5,8 @@ package aws.smithy.kotlin.runtime.serde.json -import aws.smithy.kotlin.runtime.serde.* -import aws.smithy.kotlin.runtime.util.* +import aws.smithy.kotlin.runtime.collections.* +import aws.smithy.kotlin.runtime.serde.DeserializationException private val DIGITS = ('0'..'9').toSet() private val EXP = setOf('e', 'E') diff --git a/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/XmlSerializer.kt b/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/XmlSerializer.kt index 339c835b5..b22dc1d63 100644 --- a/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/XmlSerializer.kt +++ b/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/XmlSerializer.kt @@ -5,13 +5,13 @@ package aws.smithy.kotlin.runtime.serde.xml import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.* import aws.smithy.kotlin.runtime.content.BigDecimal import aws.smithy.kotlin.runtime.content.BigInteger import aws.smithy.kotlin.runtime.content.Document import aws.smithy.kotlin.runtime.serde.* import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.TimestampFormat -import aws.smithy.kotlin.runtime.util.* /** * Provides serialization for the XML message format. diff --git a/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/dom/XmlNode.kt b/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/dom/XmlNode.kt index 89f1ba531..0d1db20e7 100644 --- a/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/dom/XmlNode.kt +++ b/runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/dom/XmlNode.kt @@ -6,11 +6,14 @@ package aws.smithy.kotlin.runtime.serde.xml.dom import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.ListStack +import aws.smithy.kotlin.runtime.collections.pop +import aws.smithy.kotlin.runtime.collections.push +import aws.smithy.kotlin.runtime.collections.top import aws.smithy.kotlin.runtime.serde.DeserializationException import aws.smithy.kotlin.runtime.serde.xml.XmlStreamReader import aws.smithy.kotlin.runtime.serde.xml.XmlToken import aws.smithy.kotlin.runtime.serde.xml.xmlStreamReader -import aws.smithy.kotlin.runtime.util.* /** * DOM representation of an XML document diff --git a/runtime/smithy-client/api/smithy-client.api b/runtime/smithy-client/api/smithy-client.api index 20014eea9..601099927 100644 --- a/runtime/smithy-client/api/smithy-client.api +++ b/runtime/smithy-client/api/smithy-client.api @@ -168,12 +168,12 @@ public final class aws/smithy/kotlin/runtime/client/SdkClientFactory$DefaultImpl public final class aws/smithy/kotlin/runtime/client/SdkClientOption { public static final field INSTANCE Laws/smithy/kotlin/runtime/client/SdkClientOption; - public final fun getClientName ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getEndpointDiscoveryEnabled ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getIdempotencyTokenProvider ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getLogMode ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getOperationName ()Laws/smithy/kotlin/runtime/util/AttributeKey; - public final fun getServiceName ()Laws/smithy/kotlin/runtime/util/AttributeKey; + public final fun getClientName ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getEndpointDiscoveryEnabled ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getIdempotencyTokenProvider ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getLogMode ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getOperationName ()Laws/smithy/kotlin/runtime/collections/AttributeKey; + public final fun getServiceName ()Laws/smithy/kotlin/runtime/collections/AttributeKey; } public final class aws/smithy/kotlin/runtime/client/SdkClientOptionKt { @@ -185,9 +185,9 @@ public final class aws/smithy/kotlin/runtime/client/endpoints/Endpoint { public fun (Ljava/lang/String;)V public final fun component1 ()Laws/smithy/kotlin/runtime/net/url/Url; public final fun component2 ()Laws/smithy/kotlin/runtime/collections/ValuesMap; - public final fun component3 ()Laws/smithy/kotlin/runtime/util/Attributes; - public final fun copy (Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; - public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/client/endpoints/Endpoint;Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/util/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; + public final fun component3 ()Laws/smithy/kotlin/runtime/collections/Attributes; + public final fun copy (Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/collections/Attributes;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; + public static synthetic fun copy$default (Laws/smithy/kotlin/runtime/client/endpoints/Endpoint;Laws/smithy/kotlin/runtime/net/url/Url;Laws/smithy/kotlin/runtime/collections/ValuesMap;Laws/smithy/kotlin/runtime/collections/Attributes;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/client/endpoints/Endpoint; public fun equals (Ljava/lang/Object;)Z public final fun getHeaders ()Laws/smithy/kotlin/runtime/collections/ValuesMap; public final fun getUri ()Laws/smithy/kotlin/runtime/net/url/Url; diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/SdkClientOption.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/SdkClientOption.kt index 45f4fea80..49045379b 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/SdkClientOption.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/SdkClientOption.kt @@ -6,8 +6,8 @@ package aws.smithy.kotlin.runtime.client import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.AttributeKey import aws.smithy.kotlin.runtime.operation.ExecutionContext -import aws.smithy.kotlin.runtime.util.AttributeKey /** * Common client execution options diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt index 162a8e63d..7ab38b36f 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/Endpoint.kt @@ -6,10 +6,10 @@ package aws.smithy.kotlin.runtime.client.endpoints import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.collections.ValuesMap +import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.net.url.Url -import aws.smithy.kotlin.runtime.util.Attributes -import aws.smithy.kotlin.runtime.util.emptyAttributes /** * Represents the endpoint a service client should make API operation calls to. diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/SigningContext.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/SigningContext.kt index fe84c3e9b..f05ea4fad 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/SigningContext.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/endpoints/SigningContext.kt @@ -6,7 +6,7 @@ package aws.smithy.kotlin.runtime.client.endpoints import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.auth.AuthOption -import aws.smithy.kotlin.runtime.util.AttributeKey +import aws.smithy.kotlin.runtime.collections.AttributeKey /** * Static attribute key for AWS endpoint auth schemes that can influence the signing context diff --git a/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt b/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt index bb672aea1..6a51615e1 100644 --- a/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt +++ b/runtime/smithy-test/common/src/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTest.kt @@ -92,7 +92,7 @@ private suspend fun assertRequest(expected: ExpectedHttpRequest, actual: HttpReq expected.queryParams.forEach { (key, value) -> val actualValues = actualParams[key] assertNotNull(actualValues, "expected query parameter `$key`; no values found") - assertTrue(value in actualValues, "expected query name value pair not found: `$key:$value`") + assertTrue(value in actualValues, "Query parameter `$key` does not contain expected value `$value`. Actual values: ${actualValues.joinToString(", ", "[", "]")}") } expected.forbiddenQueryParams.forEach { diff --git a/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt b/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt index a3a42e704..140fea8d2 100644 --- a/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt +++ b/runtime/smithy-test/common/test/aws/smithy/kotlin/runtime/smithy/test/HttpRequestTestBuilderTest.kt @@ -82,7 +82,7 @@ class HttpRequestTestBuilderTest { } } } - ex.message.shouldContain("expected query name value pair not found: `Hi:Hello%20there`") + ex.message.shouldContain("Query parameter `Hi` does not contain expected value `Hello%20there`. Actual values: [Hello]") } @Test From 848ed14e395a34619517ab76caba7677cd658188 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:14:07 +0000 Subject: [PATCH 14/17] new implementation of HTTP label encoding handling --- .../protocol/HttpBindingProtocolGenerator.kt | 24 ++---- .../protocol/HttpStringValuesMapSerializer.kt | 2 +- .../codegen/IdempotentTokenGeneratorTest.kt | 10 +-- .../HttpBindingProtocolGeneratorTest.kt | 38 +++------ .../HttpStringValuesMapSerializerTest.kt | 8 +- .../collections/views/MutableListView.kt | 2 +- .../collections/views/MutableMultiMapView.kt | 22 +++++- .../kotlin/runtime/net/url/QueryParameters.kt | 25 +++++- .../kotlin/runtime/text/encoding/Encodable.kt | 5 +- .../views/MutableMultiMapViewTest.kt | 79 +++++++++++++++++++ .../runtime/net/url/QueryParametersTest.kt | 33 ++++++++ .../kotlin/runtime/net/url/UrlParsingTest.kt | 4 + 12 files changed, 186 insertions(+), 66 deletions(-) create mode 100644 runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapViewTest.kt diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt index a93bc1236..bda2d1f58 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGenerator.kt @@ -402,10 +402,11 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { } if (queryBindings.isNotEmpty() || queryMapBindings.isNotEmpty()) { - writer.withBlock("parameters.encodedParameters {", "}") { - // Set up a temporary map for the query labels - writer.write("val labels = #T()", RuntimeTypes.Core.Collections.mutableMultiMapOf) - + writer.withBlock( + "parameters.decodedParameters(#T.SmithyLabel) {", + "}", + RuntimeTypes.Core.Text.Encoding.PercentEncoding, + ) { // render length check if applicable queryBindings.forEach { binding -> renderNonBlankGuard(ctx, binding.member, writer) } @@ -417,8 +418,8 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { val target = ctx.model.expectShape(it.member.target) val valueTarget = ctx.model.expectShape(target.value.target) val fn = when (valueTarget.type) { - ShapeType.STRING -> "labels.add" - ShapeType.LIST, ShapeType.SET -> "labels.addAll" + ShapeType.STRING -> "add" + ShapeType.LIST, ShapeType.SET -> "addAll" else -> throw CodegenException("unexpected value type for httpQueryParams map") } @@ -438,17 +439,6 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator { } .dedent() } - - // Transfer encoded labels into query params map - openBlock("labels") - write(".entries") - withBlock(".associateTo(this) { (key, values) ->", "}") { - write( - "#1T.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), #1T.SmithyLabel::encode)", - RuntimeTypes.Core.Text.Encoding.PercentEncoding, - ) - } - dedent() } } } diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt index c53bd13bb..c82be5333 100644 --- a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializer.kt @@ -215,7 +215,7 @@ class HttpStringValuesMapSerializer( private val HttpBinding.Location.addFnName: String get() = when (this) { - HttpBinding.Location.QUERY, HttpBinding.Location.QUERY_PARAMS -> "labels.add" // uses MutableMultiMap + HttpBinding.Location.QUERY, HttpBinding.Location.QUERY_PARAMS -> "add" // uses MutableMultiMap else -> "append" // uses ValuesMapBuilder } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt index 936052944..431cf08f4 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/IdempotentTokenGeneratorTest.kt @@ -64,14 +64,8 @@ internal class AllocateWidgetQueryOperationSerializer: HttpSerialize() - labels.add("clientToken", (input.clientToken ?: context.idempotencyTokenProvider.generateToken())) - labels - .entries - .associateTo(this) { (key, values) -> - PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) - } + parameters.decodedParameters(PercentEncoding.SmithyLabel) { + add("clientToken", (input.clientToken ?: context.idempotencyTokenProvider.generateToken())) } } diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt index cab6b01e7..ea5715625 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpBindingProtocolGeneratorTest.kt @@ -52,14 +52,8 @@ internal class SmokeTestOperationSerializer: HttpSerialize { "$label1".split("/").mapTo(this) { PercentEncoding.SmithyLabel.encode(it) } add(PercentEncoding.Path.encode("foo")) } - parameters.encodedParameters { - val labels = mutableMultiMapOf() - if (input.query1 != null) labels.add("Query1", input.query1) - labels - .entries - .associateTo(this) { (key, values) -> - PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) - } + parameters.decodedParameters(PercentEncoding.SmithyLabel) { + if (input.query1 != null) add("Query1", input.query1) } } @@ -266,15 +260,9 @@ internal class TimestampInputOperationSerializer: HttpSerialize() - if (input.queryTimestamp != null) labels.add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) - if (input.queryTimestampList?.isNotEmpty() == true) labels.addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) - labels - .entries - .associateTo(this) { (key, values) -> - PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) - } + parameters.decodedParameters(PercentEncoding.SmithyLabel) { + if (input.queryTimestamp != null) add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) + if (input.queryTimestampList?.isNotEmpty() == true) addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) } } @@ -577,18 +565,12 @@ internal class SmokeTestOperationDeserializer: HttpDeserialize() + parameters.decodedParameters(PercentEncoding.SmithyLabel) { require(input.garply?.isNotBlank() == true) { "garply is bound to the URI and must be a non-blank value" } - if (input.corge != null) labels.add("corge", input.corge) - if (input.garply != null) labels.add("garply", input.garply) - if (input.grault != null) labels.add("grault", input.grault) - if (input.quux != null) labels.add("quux", "$quux") - labels - .entries - .associateTo(this) { (key, values) -> - PercentEncoding.SmithyLabel.encode(key) to values.mapTo(mutableListOf(), PercentEncoding.SmithyLabel::encode) - } + if (input.corge != null) add("corge", input.corge) + if (input.garply != null) add("garply", input.garply) + if (input.grault != null) add("grault", input.grault) + if (input.quux != null) add("quux", "$quux") } """ diff --git a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt index 4b0ccf243..285fa1414 100644 --- a/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt +++ b/codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpStringValuesMapSerializerTest.kt @@ -72,7 +72,7 @@ class HttpStringValuesMapSerializerTest { contents.assertBalancedBracesAndParens() val expectedContents = """ - if (input.qInt != 0) labels.add("q-int", "${'$'}{input.qInt}") + if (input.qInt != 0) add("q-int", "${'$'}{input.qInt}") """.trimIndent() contents.shouldContainOnlyOnceWithDiff(expectedContents) } @@ -84,7 +84,7 @@ class HttpStringValuesMapSerializerTest { contents.assertBalancedBracesAndParens() val expectedContents = """ - labels.add("q-int", "${'$'}{input.qInt}") + add("q-int", "${'$'}{input.qInt}") """.trimIndent() contents.shouldContainOnlyOnceWithDiff(expectedContents) } @@ -190,8 +190,8 @@ class HttpStringValuesMapSerializerTest { val queryContents = getTestContents(defaultModel, "com.test#TimestampInput", HttpBinding.Location.QUERY) val expectedQueryContents = """ - if (input.queryTimestamp != null) labels.add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) - if (input.queryTimestampList?.isNotEmpty() == true) labels.addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) + if (input.queryTimestamp != null) add("qtime", input.queryTimestamp.format(TimestampFormat.ISO_8601)) + if (input.queryTimestampList?.isNotEmpty() == true) addAll("qtimeList", input.queryTimestampList.map { it.format(TimestampFormat.ISO_8601) }) """.trimIndent() queryContents.shouldContainOnlyOnceWithDiff(expectedQueryContents) } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt index 9730e14c9..8f21d485b 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableListView.kt @@ -5,7 +5,7 @@ package aws.smithy.kotlin.runtime.collections.views internal open class MutableListView( - private val src: MutableList, + internal val src: MutableList, private val src2Dest: (Src) -> Dest, private val dest2Src: (Dest) -> Src, ) : MutableList, ListView(src, src2Dest, dest2Src) { diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt index 433812ef4..2d08315e7 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapView.kt @@ -8,7 +8,8 @@ import aws.smithy.kotlin.runtime.collections.Entry import aws.smithy.kotlin.runtime.collections.MutableMultiMap import kotlin.jvm.JvmName -// TODO is there a way to inherit [MultiMap]? Seems tricky because of the `out` param... +// FIXME As written, `getOrPut` doesn't work correctly on views because it can return a reference to the `defaultValue` +// but that's ignored by views because we need to create the "canonical" data source in the `src` map. internal class MutableMultiMapView( private val src: MutableMultiMap, private val kSrc2Dest: (KSrc) -> KDest, @@ -17,9 +18,24 @@ internal class MutableMultiMapView( private val vDest2Src: (VDest) -> VSrc, ) : MutableMultiMap { private val vListSrc2Dest: (MutableList) -> MutableList = { it.asView(vSrc2Dest, vDest2Src) } - private val vListDest2Src: (MutableList) -> MutableList = { it.asView(vDest2Src, vSrc2Dest) } - private fun ensureKey(key: KDest) = getOrPut(key, ::mutableListOf) + private val vListDest2Src: (MutableList) -> MutableList = { + // FIXME this is not ideal because it forgets any connection to the given `MutableList` + it.mapTo(mutableListOf(), vDest2Src) + } + + private fun ensureKey(key: KDest): MutableList { + // FIXME Can't use getOrPut(key, mutableListOf()) because that will return a reference to the `mutableListOf()` + // which is disconnected from any views. Values added would disappear into the aether. + + val existingList = get(key) + return if (existingList == null) { + src[kDest2Src(key)] = mutableListOf() + getValue(key) + } else { + existingList + } + } private fun fwdEntryView(src: MutableMap.MutableEntry>) = MutableEntryView(src, kSrc2Dest, vListSrc2Dest, vListDest2Src) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt index c555d3d72..aaca48b73 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/net/url/QueryParameters.kt @@ -4,11 +4,13 @@ */ package aws.smithy.kotlin.runtime.net.url +import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.collections.MultiMap import aws.smithy.kotlin.runtime.collections.MutableMultiMap import aws.smithy.kotlin.runtime.collections.mutableMultiMapOf import aws.smithy.kotlin.runtime.collections.views.asView import aws.smithy.kotlin.runtime.text.encoding.Encodable +import aws.smithy.kotlin.runtime.text.encoding.Encoding import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding /** @@ -160,7 +162,7 @@ public class QueryParameters private constructor( val params = text.removePrefix("?") if (params.isNotEmpty()) { - params + val parsed = params .split("&") .map { segment -> val parts = segment.split("=") @@ -172,7 +174,12 @@ public class QueryParameters private constructor( } key to value } - .groupByTo(map, Pair::first, Pair::second) + .groupBy(Pair::first, Pair::second) + + // FIXME groupByTo(map, ...) should work but it relies on `getOrPut` which is an extension method + // defined in stdlib that cannot be overridden. See `MutableMultiMapView` for more details on why + // `getOrPut` doesn't work. + map.addAll(parsed) } } @@ -195,6 +202,20 @@ public class QueryParameters private constructor( decodedParameters.apply(block) } + @InternalApi + public fun decodedParameters(encoding: Encoding, block: MutableMultiMap.() -> Unit) { + val params = when (encoding) { + PercentEncoding.Query -> decodedParameters + else -> asView( + Encodable::decoded, + encoding::encodableFromDecoded, + Encodable::decoded, + encoding::encodableFromDecoded, + ) + } + params.apply(block) + } + /** * Gets the query parameters as encoded keys/values. Changes to this map are reflected in the * [decodedParameters] map as well. diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt index 4820c5c3d..ee3a3751c 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/text/encoding/Encodable.kt @@ -39,13 +39,14 @@ public class Encodable internal constructor( if (decoded != other.decoded) return false if (encoded != other.encoded) return false - return encoding == other.encoding + // return encoding == other.encoding + return true } override fun hashCode(): Int { var result = decoded.hashCode() result = 31 * result + encoded.hashCode() - result = 31 * result + encoding.hashCode() + // result = 31 * result + encoding.hashCode() return result } diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapViewTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapViewTest.kt new file mode 100644 index 000000000..6a56c4c23 --- /dev/null +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/collections/views/MutableMultiMapViewTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.collections.views + +import aws.smithy.kotlin.runtime.collections.MutableMultiMap +import aws.smithy.kotlin.runtime.collections.mutableMultiMapOf +import kotlin.test.Test +import kotlin.test.assertTrue + +class MutableMultiMapViewTest { + @Test + fun testCompetingViews() { + val src = mutableMultiMapOf() + src.add(5, "five") + + val viewA = src.asView(stringify, parse, capitalize, decapitalize) + val viewB = src.asView(multiply, divide, reverse, reverse) + + fun assertContains(key: Int, value: String) { + assertTrue(src.contains(key, value), "Expected src to contain $key→$value") + + val aKey = stringify(key) + val aVal = capitalize(value) + assertTrue(viewA.contains(aKey, aVal), "Expected viewA to contain $aKey→$aVal") + + val bKey = multiply(key) + val bVal = reverse(value) + assertTrue(viewB.contains(bKey, bVal), "Expected viewB to contain $bKey→$bVal") + } + + try { + assertContains(5, "five") + + viewA.add("5", "CINCO") + assertContains(5, "five") + assertContains(5, "cinco") + + viewB.add(500, "qnic") + assertContains(5, "five") + assertContains(5, "cinco") + assertContains(5, "cinq") + + viewA.add("8", "EIGHT") + assertContains(8, "eight") + + viewB.add(800, "ohco") + assertContains(8, "eight") + assertContains(8, "ocho") + + src.add(8, "huit") + assertContains(8, "eight") + assertContains(8, "ocho") + assertContains(8, "huit") + } catch (e: AssertionError) { + println("State of src : ${src.dumpMultiMap()}") + println("State of viewA : ${viewA.dumpMultiMap()}") + println("State of viewB : ${viewB.dumpMultiMap()}") + throw e + } + } +} + +private fun MutableMultiMap<*, *>.dumpMultiMap() = entries.joinToString(", ", "{ ", " }") { (key, values) -> + val valuesString = values.joinToString(", ", "[ ", " ]") + "$key: $valuesString" +} + +private val stringify: (Int) -> String = Int::toString +private val parse: (String) -> Int = String::toInt + +private val capitalize: (String) -> String = String::uppercase +private val decapitalize: (String) -> String = String::lowercase + +private val multiply: (Int) -> Int = { it * 100 } +private val divide: (Int) -> Int = { it / 100 } + +private val reverse: (String) -> String = String::reversed diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt index 5d1d2a9b4..3b48591a7 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/QueryParametersTest.kt @@ -4,8 +4,11 @@ */ package aws.smithy.kotlin.runtime.net.url +import aws.smithy.kotlin.runtime.collections.MutableMultiMap +import aws.smithy.kotlin.runtime.text.encoding.PercentEncoding import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class QueryParametersTest { @Test @@ -14,4 +17,34 @@ class QueryParametersTest { val expected = QueryParameters { forceQuerySeparator = true } assertEquals(expected, actual) } + + @Test + fun testDecodedParametersAlternateEncoding() { + val paramString = QueryParameters { + decodedParameters { + add("a", "green apple") + add("b", "yellow banana") + } + + decodedParameters(PercentEncoding.FormUrl) { + add("a", "red apple") + } + + val allValues = entryValues.toSet() + listOf( + "a" to "green%20apple", + "b" to "yellow%20banana", + "a" to "red+apple", + ).forEach { (key, value) -> + assertTrue(allValues.any { it.key.encoded == key && it.value.encoded == value }, dumpMultiMap()) + } + }.toString() + + assertEquals("?a=green%20apple&a=red+apple&b=yellow%20banana", paramString) + } +} + +private fun MutableMultiMap<*, *>.dumpMultiMap() = entries.joinToString("", "{\n", "}\n") { (key, values) -> + val valuesString = values.joinToString("") { " $it\n" } + " $key: [\n$valuesString ],\n" } diff --git a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt index 3e4828773..ebd1d06fb 100644 --- a/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt +++ b/runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/net/url/UrlParsingTest.kt @@ -179,6 +179,8 @@ class UrlParsingTest { @Test fun testQuery() { + val full = Url.parse("https://host?k=v") + assertEquals( QueryParameters { decodedParameters { add("k", "v") } }, Url.parse("https://host?k=v").parameters, @@ -229,6 +231,8 @@ class UrlParsingTest { assertEquals(0, parsed.size) assertTrue(parsed.forceQuerySeparator, "Expected forceQuery=true for $url") } + + println(full) } @Test From 2df6028b11ca1e1c91f2a5f88e0fad45f8f4456b Mon Sep 17 00:00:00 2001 From: Matas Lauzadis Date: Wed, 22 Nov 2023 10:39:14 -0600 Subject: [PATCH 15/17] Add changelog --- .changes/1cd7d354-501b-439a-a01a-3e884558383a.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/1cd7d354-501b-439a-a01a-3e884558383a.json diff --git a/.changes/1cd7d354-501b-439a-a01a-3e884558383a.json b/.changes/1cd7d354-501b-439a-a01a-3e884558383a.json new file mode 100644 index 000000000..6cfb2878b --- /dev/null +++ b/.changes/1cd7d354-501b-439a-a01a-3e884558383a.json @@ -0,0 +1,5 @@ +{ + "id": "1cd7d354-501b-439a-a01a-3e884558383a", + "type": "feature", + "description": "Overhaul URL APIs to clarify content encoding, when data is in which state, and to reduce the number of times data is encoded/decoded" +} \ No newline at end of file From bb5d2e426ca827cdf384d53031db90900dfad806 Mon Sep 17 00:00:00 2001 From: Matas Lauzadis Date: Wed, 22 Nov 2023 10:40:26 -0600 Subject: [PATCH 16/17] Bump versions for a breaking change --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 48b4867ba..ccaaaeaea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,10 +8,10 @@ kotlin.native.ignoreDisabledTargets=true org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=1G # SDK -sdkVersion=0.29.1-SNAPSHOT +sdkVersion=0.30.0-SNAPSHOT # codegen -codegenVersion=0.29.1-SNAPSHOT +codegenVersion=0.30.0-SNAPSHOT # kotlin kotlinVersion=1.9.20 \ No newline at end of file From 3ddfe5830789e55166649cc4656d420e7fbc9bc4 Mon Sep 17 00:00:00 2001 From: Matas Lauzadis Date: Wed, 22 Nov 2023 10:55:31 -0600 Subject: [PATCH 17/17] Breaking change --- .changes/1cd7d354-501b-439a-a01a-3e884558383a.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/1cd7d354-501b-439a-a01a-3e884558383a.json b/.changes/1cd7d354-501b-439a-a01a-3e884558383a.json index 6cfb2878b..590dc02dd 100644 --- a/.changes/1cd7d354-501b-439a-a01a-3e884558383a.json +++ b/.changes/1cd7d354-501b-439a-a01a-3e884558383a.json @@ -1,5 +1,5 @@ { "id": "1cd7d354-501b-439a-a01a-3e884558383a", "type": "feature", - "description": "Overhaul URL APIs to clarify content encoding, when data is in which state, and to reduce the number of times data is encoded/decoded" + "description": "BREAKING: Overhaul URL APIs to clarify content encoding, when data is in which state, and to reduce the number of times data is encoded/decoded" } \ No newline at end of file