Skip to content

Commit d1cc796

Browse files
committed
Merge remote-tracking branch 'origin/main' into release
2 parents 01c9ca2 + 2a39f9c commit d1cc796

File tree

39 files changed

+49297
-515
lines changed

39 files changed

+49297
-515
lines changed

gradle.properties

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ kotlinxBenchmarkVersion=0.3.1
3131

3232
# serialization
3333
kamlVersion=0.36.0
34-
xmlpullVersion=1.1.3.1
35-
xpp3Version=1.1.6
3634

3735
# logging
3836
kotlinLoggingVersion=2.0.3

runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/middleware/ResolveEndpoint.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ class ResolveEndpoint(
3131
@InternalApi
3232
fun setRequestEndpoint(req: SdkHttpRequest, endpoint: Endpoint) {
3333
val hostPrefix = req.context.getOrNull(HttpOperationContext.HostPrefix)
34-
val hostname = if (hostPrefix != null) "${hostPrefix}${endpoint.uri.host}" else endpoint.uri.host
34+
val hostname = if (hostPrefix != null && !endpoint.isHostnameImmutable) {
35+
"$hostPrefix${endpoint.uri.host}"
36+
} else {
37+
endpoint.uri.host
38+
}
39+
3540
req.subject.url.scheme = endpoint.uri.scheme
3641
req.subject.url.host = hostname
3742
req.subject.url.port = endpoint.uri.port

runtime/protocol/http/common/test/aws/smithy/kotlin/runtime/http/middleware/ResolveEndpointTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,22 @@ class ResolveEndpointTest {
100100
assertEquals("/operation", actual.url.path)
101101
}
102102

103+
@Test
104+
fun testSkipHostPrefixForImmutableHostnames() = runTest {
105+
val op = newTestOperation<Unit, Unit>(HttpRequestBuilder().apply { url.path = "/operation" }, Unit)
106+
val endpoint = Endpoint(uri = Url.parse("http://api.test.com"), isHostnameImmutable = true)
107+
val resolver = EndpointResolver { endpoint }
108+
op.install(ResolveEndpoint(resolver))
109+
op.context[HttpOperationContext.HostPrefix] = "prefix."
110+
111+
op.roundTrip(client, Unit)
112+
val actual = op.context[HttpOperationContext.HttpCallList].first().request
113+
114+
assertEquals("api.test.com", actual.url.host)
115+
assertEquals(Protocol.HTTP, actual.url.scheme)
116+
assertEquals("/operation", actual.url.path)
117+
}
118+
103119
@Test
104120
fun testEndpointPathPrefixWithNonEmptyPath() = runTest {
105121
val op = newTestOperation<Unit, Unit>(HttpRequestBuilder().apply { url.path = "/operation" }, Unit)

runtime/serde/serde-xml/build.gradle.kts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ description = "XML serialization and deserialization for Smithy services generat
77
extra["displayName"] = "Smithy :: Kotlin :: Serde :: XML"
88
extra["moduleName"] = "aws.smithy.kotlin.runtime.serde.xml"
99

10-
val xmlpullVersion: String by project
11-
val xpp3Version: String by project
1210
val slf4jVersion: String by project
1311

1412
kotlin {
@@ -18,13 +16,6 @@ kotlin {
1816
api(project(":runtime:serde"))
1917
}
2018
}
21-
jvmMain {
22-
dependencies {
23-
implementation("xmlpull:xmlpull:$xmlpullVersion")
24-
// https://mvnrepository.com/artifact/org.ogce/xpp3
25-
implementation("org.ogce:xpp3:$xpp3Version")
26-
}
27-
}
2819

2920
all {
3021
languageSettings.optIn("aws.smithy.kotlin.runtime.util.InternalApi")

runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/XmlStreamReader.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
package aws.smithy.kotlin.runtime.serde.xml
77

8+
import aws.smithy.kotlin.runtime.serde.xml.deserialization.LexingXmlStreamReader
9+
import aws.smithy.kotlin.runtime.serde.xml.deserialization.StringTextStream
10+
import aws.smithy.kotlin.runtime.serde.xml.deserialization.XmlLexer
11+
812
/**
913
* Provides stream-style access to an XML payload. This abstraction
1014
* supports the ability to look ahead an arbitrary number of elements. It can also
@@ -22,7 +26,7 @@ interface XmlStreamReader {
2226
/**
2327
* The subtree's minimum depth is the same as the current node depth + 1.
2428
*/
25-
CHILD
29+
CHILD,
2630
}
2731
/**
2832
* Return the last token that was consumed by the reader.
@@ -39,7 +43,7 @@ interface XmlStreamReader {
3943
/**
4044
* Return the next actionable token or null if stream is exhausted.
4145
*
42-
* @throws XmlGenerationException upon any error.
46+
* @throws [aws.smithy.kotlin.runtime.serde.DeserializationException] upon any error.
4347
*/
4448
fun nextToken(): XmlToken?
4549

@@ -63,17 +67,20 @@ interface XmlStreamReader {
6367
*/
6468
inline fun <reified T : XmlToken> XmlStreamReader.seek(selectionPredicate: (T) -> Boolean = { true }): T? {
6569
var token: XmlToken? = lastToken
66-
var foundMatch = false
6770

68-
while (token != null && !foundMatch) {
69-
foundMatch = if (token is T) selectionPredicate.invoke(token) else false
71+
do {
72+
val foundMatch = if (token is T) selectionPredicate.invoke(token) else false
7073
if (!foundMatch) token = nextToken()
71-
}
74+
} while (token != null && !foundMatch)
7275

7376
return token as T?
7477
}
7578

76-
/*
77-
* Creates an [XmlStreamReader] instance
78-
*/
79-
expect fun xmlStreamReader(payload: ByteArray): XmlStreamReader
79+
/**
80+
* Creates an [XmlStreamReader] instance
81+
*/
82+
fun xmlStreamReader(payload: ByteArray): XmlStreamReader {
83+
val stream = StringTextStream(payload.decodeToString())
84+
val lexer = XmlLexer(stream)
85+
return LexingXmlStreamReader(lexer)
86+
}

runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/XmlStreamWriter.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@
55

66
package aws.smithy.kotlin.runtime.serde.xml
77

8+
import aws.smithy.kotlin.runtime.serde.xml.serialization.BufferingXmlStreamWriter
9+
import aws.smithy.kotlin.runtime.util.InternalApi
10+
811
/**
912
* Defines an interface to serialization of an XML Infoset.
1013
*/
1114
interface XmlStreamWriter {
1215

1316
/**
14-
* Write xml declaration with encoding (if encoding not null)
15-
* and standalone flag (if standalone not null)
16-
* This method can only be called just after setOutput.
17+
* Write the XML declaration.
1718
*/
18-
fun startDocument(encoding: String? = null, standalone: Boolean? = null)
19+
fun startDocument()
1920

2021
/**
2122
* Finish writing. All unclosed start tags will be closed and output
22-
* will be flushed. After calling this method no more output can be
23-
* serialized until next call to setOutput()
23+
* will be flushed.
2424
*/
2525
fun endDocument()
2626

@@ -63,9 +63,14 @@ interface XmlStreamWriter {
6363
fun namespacePrefix(uri: String, prefix: String? = null)
6464

6565
/**
66-
* XML content will be constructed in this UTF-8 encoded byte array.
66+
* Gets the byte serialization for this writer. Note that this will call [endDocument] first, closing all open tags.
6767
*/
6868
val bytes: ByteArray
69+
70+
/**
71+
*
72+
*/
73+
val text: String
6974
}
7075

7176
fun XmlStreamWriter.text(text: Number) {
@@ -75,4 +80,5 @@ fun XmlStreamWriter.text(text: Number) {
7580
/*
7681
* Creates a [XmlStreamWriter] instance to write XML
7782
*/
78-
internal expect fun xmlStreamWriter(pretty: Boolean = false): XmlStreamWriter
83+
@InternalApi
84+
fun xmlStreamWriter(pretty: Boolean = false): XmlStreamWriter = BufferingXmlStreamWriter(pretty)

runtime/serde/serde-xml/common/src/aws/smithy/kotlin/runtime/serde/xml/XmlToken.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package aws.smithy.kotlin.runtime.serde.xml
77

88
/**
9-
* Raw tokens produced when reading a XML document as a stream
9+
* Raw tokens produced when reading an XML document as a stream
1010
*/
1111
sealed class XmlToken {
1212

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
package aws.smithy.kotlin.runtime.serde.xml.deserialization
6+
7+
import aws.smithy.kotlin.runtime.serde.xml.XmlToken
8+
9+
/**
10+
* Describes the internal state of an [XmlLexer].
11+
*/
12+
internal sealed class LexerState {
13+
/**
14+
* The node depth at which the lexer is parsing tokens. Like the concept of depth in [XmlToken], this depth is 1 at
15+
* the root (but 0 outside the root).
16+
*/
17+
abstract val depth: Int
18+
19+
/**
20+
* The initial state at the beginning of a document before reading any tags, DTD, or prolog.
21+
*/
22+
object Initial : LexerState() {
23+
override val depth = 0
24+
}
25+
26+
/**
27+
* The lexer is expecting the root tag next.
28+
*/
29+
object BeforeRootTag : LexerState() {
30+
override val depth = 0
31+
}
32+
33+
/**
34+
* Describes the state of being inside a tag.
35+
*/
36+
sealed class Tag : LexerState() {
37+
abstract val name: XmlToken.QualifiedName
38+
abstract val parent: OpenTag?
39+
40+
/**
41+
* The lexer is inside a tag. The next close tag should match the name of this tag.
42+
*/
43+
data class OpenTag(
44+
override val name: XmlToken.QualifiedName,
45+
override val parent: OpenTag?,
46+
val seenChildren: Boolean,
47+
) : Tag() {
48+
override val depth: Int = (parent?.depth ?: 0) + 1
49+
}
50+
51+
/**
52+
* The lexer has read a self-closing tag (e.g., '<foo />') but only returned the [XmlToken.BeginElement] token
53+
* to the caller. The subsequent [XmlLexer.parseNext] call will return an [XmlToken.EndElement] without
54+
* actually reading more from the source.
55+
*/
56+
data class EmptyTag(override val name: XmlToken.QualifiedName, override val parent: OpenTag?) : Tag() {
57+
override val depth: Int = (parent?.depth ?: 0) + 1
58+
}
59+
}
60+
61+
/**
62+
* The end of the document is reached. No more data is available.
63+
*/
64+
object EndOfDocument : LexerState() {
65+
override val depth = 0
66+
}
67+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
package aws.smithy.kotlin.runtime.serde.xml.deserialization
6+
7+
import aws.smithy.kotlin.runtime.serde.DeserializationException
8+
import aws.smithy.kotlin.runtime.serde.xml.XmlStreamReader
9+
import aws.smithy.kotlin.runtime.serde.xml.XmlToken
10+
import aws.smithy.kotlin.runtime.serde.xml.terminates
11+
12+
/**
13+
* An [XmlStreamReader] that provides [XmlToken] elements from an [XmlLexer]. This class internally maintains a peek
14+
* state, [lastToken], etc., but delegates all parsing operations to the scanner.
15+
* @param source The [XmlLexer] to use for XML parsing.
16+
*/
17+
class LexingXmlStreamReader(private val source: XmlLexer) : XmlStreamReader {
18+
private val peekQueue = ArrayDeque<XmlToken>()
19+
20+
/**
21+
* Throws a [DeserializationException] with the given message and location string.
22+
* @param msg The error message to include with the exception.
23+
*/
24+
@Suppress("NOTHING_TO_INLINE")
25+
internal inline fun error(msg: String): Nothing = source.error(msg)
26+
27+
override var lastToken: XmlToken? = null
28+
private set
29+
30+
override fun nextToken(): XmlToken? =
31+
(peekQueue.removeFirstOrNull() ?: source.parseNext()).also { lastToken = it }
32+
33+
override fun peek(index: Int): XmlToken? {
34+
while (index > peekQueue.size && !source.endOfDocument) {
35+
peekQueue.addLast(source.parseNext()!!)
36+
}
37+
return peekQueue.getOrNull(index - 1)
38+
}
39+
40+
override fun skipNext() {
41+
val peekToken = peek(1) ?: return
42+
val startDepth = peekToken.depth
43+
44+
tailrec fun scanUntilDepth(from: XmlToken?) {
45+
when {
46+
from == null || from is XmlToken.EndDocument -> return // End of document
47+
from is XmlToken.EndElement && from.depth == startDepth -> return // Returned to original start depth
48+
else -> scanUntilDepth(nextToken()) // Keep scannin'!
49+
}
50+
}
51+
52+
scanUntilDepth(nextToken())
53+
}
54+
55+
override fun subTreeReader(subtreeStartDepth: XmlStreamReader.SubtreeStartDepth): XmlStreamReader =
56+
if (peek(1).terminates(lastToken)) {
57+
// Special case—return an empty subtree _and_ advance the token.
58+
nextToken()
59+
EmptyXmlStreamReader(this)
60+
} else {
61+
ChildXmlStreamReader(this, subtreeStartDepth)
62+
}
63+
}
64+
65+
/**
66+
* A child (i.e., subtree) XML stream reader that terminates after returning to the depth at which it started.
67+
* @param parent The [LexingXmlStreamReader] upon which this child reader is based.
68+
* @param subtreeStartDepth The depth termination method.
69+
*/
70+
private class ChildXmlStreamReader(
71+
private val parent: LexingXmlStreamReader,
72+
private val subtreeStartDepth: XmlStreamReader.SubtreeStartDepth,
73+
) : XmlStreamReader {
74+
override val lastToken: XmlToken?
75+
get() = parent.lastToken
76+
77+
private val minimumDepth = when (subtreeStartDepth) {
78+
XmlStreamReader.SubtreeStartDepth.CHILD -> lastToken?.depth?.plus(1)
79+
XmlStreamReader.SubtreeStartDepth.CURRENT -> lastToken?.depth
80+
} ?: error("Unable to determine depth of last node")
81+
82+
/**
83+
* Throws a [DeserializationException] with the given message and location string.
84+
* @param msg The error message to include with the exception.
85+
*/
86+
@Suppress("NOTHING_TO_INLINE")
87+
inline fun error(msg: String): Nothing = parent.error(msg)
88+
89+
override fun nextToken(): XmlToken? {
90+
val next = parent.peek(1) ?: return null
91+
92+
val peekToken = when {
93+
subtreeStartDepth == XmlStreamReader.SubtreeStartDepth.CHILD && next.depth < minimumDepth -> {
94+
val subsequent = parent.peek(2) ?: return null
95+
if (subsequent.depth >= minimumDepth) parent.nextToken()
96+
subsequent
97+
}
98+
else -> next
99+
}
100+
101+
return if (peekToken.depth >= minimumDepth) parent.nextToken() else null
102+
}
103+
104+
override fun peek(index: Int): XmlToken? {
105+
val peekToken = parent.peek(index) ?: return null
106+
return if (peekToken.depth >= minimumDepth) peekToken else null
107+
}
108+
109+
override fun skipNext() = parent.skipNext()
110+
111+
override fun subTreeReader(subtreeStartDepth: XmlStreamReader.SubtreeStartDepth): XmlStreamReader =
112+
parent.subTreeReader(subtreeStartDepth)
113+
}
114+
115+
/**
116+
* An empty XML stream reader that trivially returns `null` for all [nextToken] and [peek] invocations.
117+
* @param parent The [LexingXmlStreamReader] on which this child reader is based.
118+
*/
119+
private class EmptyXmlStreamReader(private val parent: XmlStreamReader) : XmlStreamReader {
120+
override val lastToken: XmlToken?
121+
get() = parent.lastToken
122+
123+
override fun nextToken(): XmlToken? = null
124+
125+
override fun peek(index: Int): XmlToken? = null
126+
127+
override fun skipNext() = Unit
128+
129+
override fun subTreeReader(subtreeStartDepth: XmlStreamReader.SubtreeStartDepth): XmlStreamReader = this
130+
}
131+
132+
private fun <T> List<T>.getOrNull(index: Int): T? = if (index < size) this[index] else null

0 commit comments

Comments
 (0)