Skip to content

Commit

Permalink
Endpoints 2.0 Standard Library functions (#1667)
Browse files Browse the repository at this point in the history
* Endpoints 2.0 Standard Library functions

* Endpoints Standard Library Cleanups
  • Loading branch information
rcoh authored Aug 29, 2022
1 parent 63afb41 commit 0ea576f
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 0 deletions.
1 change: 1 addition & 0 deletions rust-runtime/inlineable/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ repository = "https://github.com/awslabs/smithy-rs"
"pin-project-lite" = "0.2"
"tower" = { version = "0.4.11", default_features = false }
"async-trait" = "0.1"
"url" = "2.2.2"

[dev-dependencies]
proptest = "1"
Expand Down
10 changes: 10 additions & 0 deletions rust-runtime/inlineable/src/endpoint_lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

mod arn;
mod diagnostic;
mod host;
mod parse_url;
mod substring;
153 changes: 153 additions & 0 deletions rust-runtime/inlineable/src/endpoint_lib/arn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use crate::endpoint_lib::diagnostic::DiagnosticCollector;
use std::borrow::Cow;
use std::error::Error;
use std::fmt::{Display, Formatter};

#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Arn<'a> {
partition: &'a str,
service: &'a str,
region: &'a str,
account_id: &'a str,
resource_id: Vec<&'a str>,
}

#[allow(unused)]
impl<'a> Arn<'a> {
pub(crate) fn partition(&self) -> &'a str {
self.partition
}
pub(crate) fn service(&self) -> &'a str {
self.service
}
pub(crate) fn region(&self) -> &'a str {
self.region
}
pub(crate) fn account_id(&self) -> &'a str {
self.account_id
}
pub(crate) fn resource_id(&self) -> &Vec<&'a str> {
&self.resource_id
}
}

#[derive(Debug, PartialEq)]
pub(crate) struct InvalidArn {
message: Cow<'static, str>,
}

impl InvalidArn {
fn from_static(message: &'static str) -> InvalidArn {
Self {
message: Cow::Borrowed(message),
}
}
}
impl Display for InvalidArn {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for InvalidArn {}

impl<'a> Arn<'a> {
pub(crate) fn parse(arn: &'a str) -> Result<Self, InvalidArn> {
let mut split = arn.splitn(6, ':');
let invalid_format =
|| InvalidArn::from_static("ARN must have 6 components delimited by `:`");
let arn = split.next().ok_or_else(invalid_format)?;
let partition = split.next().ok_or_else(invalid_format)?;
let service = split.next().ok_or_else(invalid_format)?;
let region = split.next().ok_or_else(invalid_format)?;
let account_id = split.next().ok_or_else(invalid_format)?;
let resource_id = split.next().ok_or_else(invalid_format)?;

if arn != "arn" {
return Err(InvalidArn::from_static(
"first component of the ARN must be `arn`",
));
}
if partition.is_empty() || service.is_empty() || resource_id.is_empty() {
return Err(InvalidArn::from_static(
"partition, service, and resource id must all be non-empty",
));
}

let resource_id = resource_id.split([':', '/']).collect::<Vec<_>>();
Ok(Self {
partition,
service,
region,
account_id,
resource_id,
})
}
}

pub(crate) fn parse_arn<'a, 'b>(input: &'a str, e: &'b mut DiagnosticCollector) -> Option<Arn<'a>> {
e.capture(Arn::parse(input))
}

#[cfg(test)]
mod test {
use super::Arn;
use crate::endpoint_lib::diagnostic::DiagnosticCollector;

#[test]
fn arn_parser() {
let arn = "arn:aws:s3:us-east-2:012345678:outpost:op-1234";
let parsed = Arn::parse(arn).expect("valid ARN");
assert_eq!(
parsed,
Arn {
partition: "aws",
service: "s3",
region: "us-east-2",
account_id: "012345678",
resource_id: vec!["outpost", "op-1234"]
}
);
}

#[test]
fn allow_slash_arns() {
let arn = "arn:aws:s3:us-east-2:012345678:outpost/op-1234";
let parsed = Arn::parse(arn).expect("valid ARN");
assert_eq!(
parsed,
Arn {
partition: "aws",
service: "s3",
region: "us-east-2",
account_id: "012345678",
resource_id: vec!["outpost", "op-1234"]
}
);
}

#[test]
fn resource_id_must_be_nonempty() {
let arn = "arn:aws:s3:us-east-2:012345678:";
Arn::parse(arn).expect_err("empty resource");
}

#[test]
fn arns_with_empty_parts() {
let arn = "arn:aws:s3:::my_corporate_bucket/Development/*";
assert_eq!(
Arn::parse(arn).expect("valid arn"),
Arn {
partition: "aws",
service: "s3",
region: "",
account_id: "",
resource_id: vec!["my_corporate_bucket", "Development", "*"]
}
);
}
}
45 changes: 45 additions & 0 deletions rust-runtime/inlineable/src/endpoint_lib/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use std::error::Error;

/// Diagnostic collector for endpoint resolution
///
/// Endpoint functions return `Option<T>`—to enable diagnostic information to flow, we capture the
/// last error that occurred.
#[derive(Debug, Default)]
pub(crate) struct DiagnosticCollector {
last_error: Option<Box<dyn Error + Send + Sync>>,
}

impl DiagnosticCollector {
/// Report an error to the collector
pub(crate) fn report_error(&mut self, err: impl Into<Box<dyn Error + Send + Sync>>) {
self.last_error = Some(err.into());
}

/// Capture a result, returning Some(t) when the input was `Ok` and `None` otherwise
pub(crate) fn capture<T, E: Into<Box<dyn Error + Send + Sync>>>(
&mut self,
err: Result<T, E>,
) -> Option<T> {
match err {
Ok(res) => Some(res),
Err(e) => {
self.report_error(e);
None
}
}
}

pub(crate) fn take_last_error(&mut self) -> Option<Box<dyn Error + Send + Sync>> {
self.last_error.take()
}

/// Create a new diagnostic collector
pub(crate) fn new() -> Self {
Self { last_error: None }
}
}
76 changes: 76 additions & 0 deletions rust-runtime/inlineable/src/endpoint_lib/host.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use crate::endpoint_lib::diagnostic::DiagnosticCollector;

pub(crate) fn is_valid_host_label(
label: &str,
allow_dots: bool,
e: &mut DiagnosticCollector,
) -> bool {
if allow_dots {
for part in label.split('.') {
if !is_valid_host_label(part, false, e) {
return false;
}
}
true
} else {
if label.is_empty() || label.len() > 63 {
e.report_error("host was too short or too long");
return false;
}
label.chars().enumerate().all(|(idx, ch)| match (ch, idx) {
('-', 0) => {
e.report_error("cannot start with `-`");
false
}
_ => ch.is_alphanumeric() || ch == '-',
})
}
}

#[cfg(test)]
mod test {
use proptest::proptest;

fn is_valid_host_label(label: &str, allow_dots: bool) -> bool {
super::is_valid_host_label(label, allow_dots, &mut DiagnosticCollector::new())
}

#[test]
fn basic_cases() {
assert_eq!(is_valid_host_label("", false), false);
assert_eq!(is_valid_host_label("", true), false);
assert_eq!(is_valid_host_label(".", true), false);
assert_eq!(is_valid_host_label("a.b", true), true);
assert_eq!(is_valid_host_label("a.b", false), false);
assert_eq!(is_valid_host_label("a.b.", true), false);
assert_eq!(is_valid_host_label("a.b.c", true), true);
assert_eq!(is_valid_host_label("a_b", true), false);
assert_eq!(is_valid_host_label(&"a".repeat(64), false), false);
assert_eq!(
is_valid_host_label(&format!("{}.{}", "a".repeat(63), "a".repeat(63)), true),
true
);
}

#[test]
fn start_bounds() {
assert_eq!(is_valid_host_label("-foo", false), false);
assert_eq!(is_valid_host_label("-foo", true), false);
assert_eq!(is_valid_host_label(".foo", true), false);
assert_eq!(is_valid_host_label("a-b.foo", true), true);
}

use crate::endpoint_lib::diagnostic::DiagnosticCollector;
use proptest::prelude::*;
proptest! {
#[test]
fn no_panics(s in any::<String>(), dots in any::<bool>()) {
is_valid_host_label(&s, dots);
}
}
}
Loading

0 comments on commit 0ea576f

Please sign in to comment.