Skip to content
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

Add support for endpoints.json #468

Merged
merged 10 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/rust-runtime/aws-endpoint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ description = "AWS Endpoint Support"
smithy-http = { path = "../../../rust-runtime/smithy-http"}
aws-types = { path = "../aws-types" }
http = "0.2.3"
regex = { version = "1", default-features = false, features = ["std"]}
134 changes: 81 additions & 53 deletions aws/rust-runtime/aws-endpoint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
* SPDX-License-Identifier: Apache-2.0.
*/

#[doc(hidden)]
pub mod partition;

#[doc(hidden)]
pub use partition::Partition;
#[doc(hidden)]
pub use partition::PartitionResolver;

use std::error::Error;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use std::sync::Arc;

use http::{HeaderValue, Uri};
use http::HeaderValue;

use aws_types::region::{Region, SigningRegion};
use aws_types::SigningService;
Expand All @@ -29,8 +36,7 @@ use std::convert::TryFrom;
#[derive(Clone)]
pub struct AwsEndpoint {
endpoint: Endpoint,
signing_service: Option<SigningService>,
signing_region: Option<SigningRegion>,
credential_scope: CredentialScope,
}

impl AwsEndpoint {
Expand Down Expand Up @@ -73,46 +79,67 @@ pub type BoxError = Box<dyn Error + Send + Sync + 'static>;
/// will be codegenerated from `endpoints.json`.
pub trait ResolveAwsEndpoint: Send + Sync {
// TODO: consider if we want modeled error variants here
fn endpoint(&self, region: &Region) -> Result<AwsEndpoint, BoxError>;
fn resolve_endpoint(&self, region: &Region) -> Result<AwsEndpoint, BoxError>;
}

/// Default AWS Endpoint Implementation
///
/// This is used as a temporary stub. Prior to GA, this will be replaced with specifically generated endpoint
/// resolvers for each service that model the endpoints for each service correctly. Some services differ
/// from the standard endpoint pattern.
pub struct DefaultAwsEndpointResolver {
service: &'static str,
#[derive(Clone, Default, Debug)]
pub struct CredentialScope {
region: Option<SigningRegion>,
service: Option<SigningService>,
}

impl DefaultAwsEndpointResolver {
pub fn for_service(service: &'static str) -> Self {
Self { service }
impl CredentialScope {
pub fn builder() -> credential_scope::Builder {
credential_scope::Builder::default()
}
}

/// An `Endpoint` can be its own resolver to support static endpoints
impl ResolveAwsEndpoint for Endpoint {
fn endpoint(&self, _region: &Region) -> Result<AwsEndpoint, BoxError> {
Ok(AwsEndpoint {
endpoint: self.clone(),
signing_service: None,
signing_region: None,
})
pub mod credential_scope {
use crate::CredentialScope;
use aws_types::region::SigningRegion;
use aws_types::SigningService;

#[derive(Debug, Default)]
pub struct Builder {
region: Option<SigningRegion>,
service: Option<SigningService>,
}

impl Builder {
pub fn region(mut self, region: &'static str) -> Self {
self.region = Some(SigningRegion::from_static(region));
self
}

pub fn service(mut self, service: &'static str) -> Self {
self.service = Some(SigningService::from_static(service));
self
}

pub fn build(self) -> CredentialScope {
CredentialScope {
region: self.region,
service: self.service,
}
}
}
}

impl ResolveAwsEndpoint for DefaultAwsEndpointResolver {
fn endpoint(&self, region: &Region) -> Result<AwsEndpoint, BoxError> {
let uri = Uri::from_str(&format!(
"https://{}.{}.amazonaws.com",
self.service,
region.as_ref(),
))?;
impl CredentialScope {
pub fn merge(&self, other: &CredentialScope) -> CredentialScope {
CredentialScope {
region: self.region.clone().or_else(|| other.region.clone()),
service: self.service.clone().or_else(|| other.service.clone()),
}
}
}

/// An `Endpoint` can be its own resolver to support static endpoints
impl ResolveAwsEndpoint for Endpoint {
fn resolve_endpoint(&self, _region: &Region) -> Result<AwsEndpoint, BoxError> {
Ok(AwsEndpoint {
endpoint: Endpoint::mutable(uri),
signing_region: Some(region.clone().into()),
signing_service: None,
endpoint: self.clone(),
credential_scope: Default::default(),
})
}
}
Expand Down Expand Up @@ -162,13 +189,14 @@ impl MapRequest for AwsEndpointStage {
.get::<Region>()
.ok_or(AwsEndpointStageError::NoRegion)?;
let endpoint = provider
.endpoint(region)
.resolve_endpoint(region)
.map_err(AwsEndpointStageError::EndpointResolutionError)?;
let signing_region = endpoint
.signing_region
.credential_scope
.region
.unwrap_or_else(|| region.clone().into());
config.insert::<SigningRegion>(signing_region);
if let Some(signing_service) = endpoint.signing_service {
if let Some(signing_service) = endpoint.credential_scope.service {
config.insert::<SigningService>(signing_service);
}
endpoint
Expand Down Expand Up @@ -199,16 +227,18 @@ mod test {
use smithy_http::middleware::MapRequest;
use smithy_http::operation;

use crate::{
set_endpoint_resolver, AwsEndpoint, AwsEndpointStage, BoxError, DefaultAwsEndpointResolver,
ResolveAwsEndpoint,
};
use crate::partition::endpoint::{Metadata, Protocol, SignatureVersion};
use crate::{set_endpoint_resolver, AwsEndpointStage, CredentialScope};
use http::header::HOST;
use smithy_http::endpoint::Endpoint;

#[test]
fn default_endpoint_updates_request() {
let provider = Arc::new(DefaultAwsEndpointResolver::for_service("kinesis"));
let provider = Arc::new(Metadata {
uri_template: "kinesis.{region}.amazonaws.com",
protocol: Protocol::Https,
credential_scope: Default::default(),
signature_versions: SignatureVersion::V4,
});
let req = http::Request::new(SdkBody::from(""));
let region = Region::new("us-east-1");
let mut req = operation::Request::new(req);
Expand Down Expand Up @@ -241,17 +271,15 @@ mod test {

#[test]
fn sets_service_override_when_set() {
struct ServiceOverrideResolver;
impl ResolveAwsEndpoint for ServiceOverrideResolver {
fn endpoint(&self, _region: &Region) -> Result<AwsEndpoint, BoxError> {
Ok(AwsEndpoint {
endpoint: Endpoint::immutable(Uri::from_static("http://www.service.com")),
signing_service: Some(SigningService::from_static("qldb-override")),
signing_region: Some(SigningRegion::from(Region::new("us-east-override"))),
})
}
}
let provider = Arc::new(ServiceOverrideResolver);
let provider = Arc::new(Metadata {
uri_template: "www.service.com",
protocol: Protocol::Http,
credential_scope: CredentialScope::builder()
.service("qldb-override")
.region("us-east-override")
.build(),
signature_versions: SignatureVersion::V4,
});
let req = http::Request::new(SdkBody::from(""));
let region = Region::new("us-east-1");
let mut req = operation::Request::new(req);
Expand Down
71 changes: 71 additions & 0 deletions aws/rust-runtime/aws-endpoint/src/partition/endpoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

use crate::{AwsEndpoint, BoxError, CredentialScope, ResolveAwsEndpoint};
use aws_types::region::Region;
use smithy_http::endpoint::Endpoint;

/// Endpoint metadata
///
/// T & P exist to support optional vs. non optional values for Protocol and URI template during
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outdated comment?

/// merging
#[derive(Debug)]
pub struct Metadata {
/// URI for the endpoint.
///
/// May contain `{region}` which will replaced with the region during endpoint construction
pub uri_template: &'static str,

/// Protocol to use for this endpoint
pub protocol: Protocol,

/// Credential scope to set for requests to this endpoint
pub credential_scope: CredentialScope,

/// Signature versions supported by this endpoint.
///
/// Currently unused since the SDK only supports SigV4
pub signature_versions: SignatureVersion,
}

#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Protocol {
Http,
Https,
}

impl Protocol {
fn as_str(&self) -> &'static str {
match self {
Protocol::Http => "http",
Protocol::Https => "https",
}
}
}

#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum SignatureVersion {
V4,
}

impl ResolveAwsEndpoint for Metadata {
fn resolve_endpoint(&self, region: &Region) -> Result<AwsEndpoint, BoxError> {
let uri = self.uri_template.replace("{region}", region.as_ref());
let uri = format!("{}://{}", self.protocol.as_str(), uri);
let endpoint = Endpoint::mutable(uri.parse()?);
let ep = AwsEndpoint {
endpoint,
credential_scope: CredentialScope {
service: self.credential_scope.service.clone(),
region: self
.credential_scope
.region
.clone()
.or_else(|| Some(region.clone().into())),
},
};
Ok(ep)
}
}
Loading