-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement httpLabel with nom #996
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,6 +107,7 @@ private class ServerHttpProtocolImplGenerator( | |
"HttpBody" to CargoDependency.HttpBody.asType(), | ||
"Hyper" to CargoDependency.Hyper.asType(), | ||
"LazyStatic" to CargoDependency.LazyStatic.asType(), | ||
"Nom" to ServerCargoDependency.Nom.asType(), | ||
"PercentEncoding" to CargoDependency.PercentEncoding.asType(), | ||
"Regex" to CargoDependency.Regex.asType(), | ||
"SerdeUrlEncoded" to ServerCargoDependency.SerdeUrlEncoded.asType(), | ||
|
@@ -572,46 +573,80 @@ private class ServerHttpProtocolImplGenerator( | |
if (pathBindings.isEmpty()) { | ||
return | ||
} | ||
val pattern = StringBuilder() | ||
val httpTrait = httpBindingResolver.httpTrait(operationShape) | ||
httpTrait.uri.segments.forEach { | ||
pattern.append("/") | ||
if (it.isLabel) { | ||
pattern.append("(?P<${it.content}>") | ||
if (it.isGreedyLabel) { | ||
pattern.append(".+") | ||
val greedyLabelIndex = httpTrait.uri.segments.indexOfFirst { it.isGreedyLabel } | ||
val segments = | ||
if (greedyLabelIndex >= 0) | ||
httpTrait.uri.segments.slice(0 until (greedyLabelIndex + 1)) | ||
else | ||
httpTrait.uri.segments | ||
val restAfterGreedyLabel = | ||
if (greedyLabelIndex >= 0) | ||
httpTrait.uri.segments.slice((greedyLabelIndex + 1) until httpTrait.uri.segments.size).joinToString(prefix = "/", separator = "/") | ||
else | ||
"" | ||
val labeledNames = segments | ||
.mapIndexed { index, segment -> | ||
if (segment.isLabel) { "m$index" } else { "_" } | ||
} | ||
.joinToString(prefix = (if (segments.size > 1) "(" else ""), separator = ",", postfix = (if (segments.size > 1) ")" else "")) | ||
val nomParser = segments | ||
.map { segment -> | ||
if (segment.isGreedyLabel) { | ||
"#{Nom}::combinator::rest::<_, #{Nom}::error::Error<&str>>" | ||
} else if (segment.isLabel) { | ||
"""#{Nom}::branch::alt::<_, _, #{Nom}::error::Error<&str>, _>((#{Nom}::bytes::complete::take_until("/"), #{Nom}::combinator::rest))""" | ||
} else { | ||
pattern.append("[^/]+") | ||
"""#{Nom}::bytes::complete::tag::<_, _, #{Nom}::error::Error<&str>>("${segment.content}")""" | ||
} | ||
pattern.append(")") | ||
} else { | ||
pattern.append(it.content) | ||
} | ||
} | ||
.joinToString( | ||
// TODO: tuple() is currently limited to 21 items | ||
prefix = if (segments.size > 1) "#{Nom}::sequence::tuple::<_, _, #{Nom}::error::Error<&str>, _>((" else "", | ||
postfix = if (segments.size > 1) "))" else "", | ||
transform = { parser -> | ||
""" | ||
#{Nom}::sequence::preceded(#{Nom}::bytes::complete::tag("/"), $parser) | ||
""".trimIndent() | ||
} | ||
) | ||
with(writer) { | ||
rustTemplate("let input_string = request.uri().path();") | ||
82marbag marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (greedyLabelIndex >= 0 && greedyLabelIndex + 1 < httpTrait.uri.segments.size) { | ||
rustTemplate( | ||
""" | ||
if !input_string.ends_with(${restAfterGreedyLabel.dq()}) { | ||
return std::result::Result::Err(#{SmithyRejection}::Deserialize( | ||
aws_smithy_http_server::rejection::Deserialize::from_err(format!("Postfix not found: {}", ${restAfterGreedyLabel.dq()})))); | ||
} | ||
let input_string = &input_string[..(input_string.len() - ${restAfterGreedyLabel.dq()}.len())]; | ||
""".trimIndent(), | ||
*codegenScope | ||
) | ||
} | ||
rustTemplate( | ||
""" | ||
#{LazyStatic}::lazy_static! { | ||
static ref RE: #{Regex}::Regex = #{Regex}::Regex::new("$pattern").unwrap(); | ||
} | ||
let (input_string, $labeledNames) = $nomParser(input_string)?; | ||
debug_assert_eq!("", input_string); | ||
""".trimIndent(), | ||
*codegenScope, | ||
*codegenScope | ||
) | ||
rustBlock("if let Some(captures) = RE.captures(request.uri().path())") { | ||
pathBindings.forEach { | ||
val deserializer = generateParsePercentEncodedStrFn(it) | ||
rustTemplate( | ||
""" | ||
if let Some(m) = captures.name("${it.locationName}") { | ||
input = input.${it.member.setterName()}( | ||
#{deserializer}(m.as_str())? | ||
segments | ||
.forEachIndexed { index, segment -> | ||
val binding = pathBindings.find { it.memberName == segment.content } | ||
if (binding != null && segment.isLabel) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In some cases, such as |
||
val deserializer = generateParsePercentEncodedStrFn(binding) | ||
rustTemplate( | ||
""" | ||
input = input.${binding.member.setterName()}( | ||
#{deserializer}(m$index)? | ||
); | ||
} | ||
""".trimIndent(), | ||
"deserializer" to deserializer, | ||
) | ||
""".trimIndent(), | ||
*codegenScope, | ||
"deserializer" to deserializer, | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why were we accepting empty path segments? I see the TypeScript sSDK is also stripping away empty path segments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Writing my thoughts down here. I guess done this way because anecdotally servers accept empty path segments e.g. https://github.com//awslabs///////smithy-typescript///commits works fine.
But when extracting labels, I'm not sure about whether this behavior should be the correct one, and I don't see it specified in the Smithy spec. I'm thinking of possible ambiguous scenarios e.g. say the URI pattern is
/foo/{label}/bar
and the server receives a request with URI path/foo//bar
. Should thelabel
field be bound to an empty string? Or should we strip away the empty segment and reject the request?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My opinion on this and interpretation of the Smithy spec is that we were indeed doing it wrong and empty path segments should not be stripped away, but I've opened an issue with the Smithy team to clarify the spec: smithy-lang/smithy#1024