Refactor general implementation into TypeSpec crates (#1710)
* Refactor general implementation into TypeSpec crates This is part of the unbranding work across languages. For now we have decided to brand around the "TypeSpec runtime" as some other Azure SDK languages have considered. * Fix build * Resolve CodeStyle check issue * Export http_response_from_body from azure_core * Fix lint, docs errors
This commit is contained in:
Родитель
9968664b53
Коммит
a523ab295a
|
@ -75,6 +75,13 @@
|
|||
"msal",
|
||||
"imds"
|
||||
]
|
||||
},
|
||||
{
|
||||
"filename": "sdk/typespec/**",
|
||||
"flagWords": [
|
||||
"azure",
|
||||
"azurite"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -2,6 +2,7 @@
|
|||
resolver = "2"
|
||||
members = [
|
||||
"sdk/typespec",
|
||||
"sdk/typespec/typespec_client_core",
|
||||
"sdk/core/azure_core",
|
||||
"sdk/identity/azure_identity",
|
||||
"eng/test/mock_transport",
|
||||
|
@ -16,12 +17,18 @@ license = "MIT"
|
|||
repository = "https://github.com/azure/azure-sdk-for-rust"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies.typespec]
|
||||
path = "sdk/typespec"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.typespec_client_core]
|
||||
path = "sdk/typespec/typespec_client_core"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.azure_core]
|
||||
version = "0.19.0"
|
||||
path = "sdk/core/azure_core"
|
||||
|
||||
[workspace.dependencies.azure_identity]
|
||||
version = "0.19.0"
|
||||
path = "sdk/identity/azure_identity"
|
||||
|
||||
[workspace.dependencies.azure_storage_common]
|
||||
|
|
|
@ -14,6 +14,8 @@ edition.workspace = true
|
|||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
typespec = { workspace = true, features = ["http", "json"] }
|
||||
typespec_client_core = { workspace = true, features = ["http", "json"] }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
pub use typespec::error::*;
|
||||
pub use typespec_client_core::error::{http_response_from_body, HttpError};
|
|
@ -1,114 +0,0 @@
|
|||
/// A convenient way to create a new error using the normal formatting infrastructure.
|
||||
#[macro_export]
|
||||
macro_rules! format_err {
|
||||
($kind:expr, $msg:literal $(,)?) => {{
|
||||
// Handle $:literal as a special case to make cargo-expanded code more
|
||||
// concise in the common case.
|
||||
$crate::error::Error::message($kind, $msg)
|
||||
}};
|
||||
($kind:expr, $msg:expr $(,)?) => {{
|
||||
$crate::error::Error::message($kind, $msg)
|
||||
}};
|
||||
($kind:expr, $msg:expr, $($arg:tt)*) => {{
|
||||
$crate::error::Error::with_message($kind, || { format!($msg, $($arg)*) })
|
||||
}};
|
||||
}
|
||||
|
||||
/// Return early with an error if a condition is not satisfied.
|
||||
#[macro_export]
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $kind:expr, $msg:literal $(,)?) => {
|
||||
if !$cond {
|
||||
return ::std::result::Result::Err($crate::format_err!($kind, $msg));
|
||||
}
|
||||
};
|
||||
($cond:expr, $kind:expr, dictate $msg:expr $(,)?) => {
|
||||
if !$cond {
|
||||
return ::std::result::Result::Err($crate::format_err!($kind, $msg));
|
||||
}
|
||||
};
|
||||
($cond:expr, $kind:expr, dictate $msg:expr, $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
return ::std::result::Result::Err($crate::format_err!($kind, $msg, $($arg)*));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Return early with an error if two expressions are not equal to each other.
|
||||
#[macro_export]
|
||||
macro_rules! ensure_eq {
|
||||
($left:expr, $right:expr, $kind:expr, $msg:literal $(,)?) => {
|
||||
$crate::ensure!($left == $right, $kind, $msg);
|
||||
};
|
||||
($left:expr, $right:expr, $kind:expr, dictate $msg:expr $(,)?) => {
|
||||
$crate::ensure!($left == $right, $kind, $msg);
|
||||
};
|
||||
($left:expr, $right:expr, $kind:expr, dictate $msg:expr, $($arg:tt)*) => {
|
||||
$crate::ensure!($left == $right, $kind, $msg, $($arg)*);
|
||||
};
|
||||
}
|
||||
|
||||
/// Return early with an error if two expressions are equal to each other.
|
||||
#[macro_export]
|
||||
macro_rules! ensure_ne {
|
||||
($left:expr, $right:expr, $kind:expr, $msg:literal $(,)?) => {
|
||||
$crate::ensure!($left != $right, $kind, $msg);
|
||||
};
|
||||
($left:expr, $right:expr, $kind:expr, dictate $msg:expr $(,)?) => {
|
||||
$crate::ensure!($left != $right, $kind, $msg);
|
||||
};
|
||||
($left:expr, $right:expr, $kind:expr, dictate $msg:expr, $($arg:tt)*) => {
|
||||
$crate::ensure!($left != $right, $kind, $msg, $($arg)*);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
struct OperationError;
|
||||
|
||||
impl Display for OperationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "OperationError")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_works() {
|
||||
fn test_ensure(predicate: bool) -> crate::Result<()> {
|
||||
ensure!(predicate, ErrorKind::Other, "predicate failed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_ensure_eq(item1: &str, item2: &str) -> crate::Result<()> {
|
||||
ensure_eq!(item1, item2, ErrorKind::Other, "predicate failed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_ensure_ne(item1: &str, item2: &str) -> crate::Result<()> {
|
||||
ensure_ne!(item1, item2, ErrorKind::Other, "predicate failed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let err = test_ensure(false).unwrap_err();
|
||||
assert_eq!(format!("{err}"), "predicate failed");
|
||||
assert_eq!(err.kind(), &ErrorKind::Other);
|
||||
|
||||
assert!(test_ensure(true).is_ok());
|
||||
|
||||
let err = test_ensure_eq("foo", "bar").unwrap_err();
|
||||
assert_eq!(format!("{err}"), "predicate failed");
|
||||
assert_eq!(err.kind(), &ErrorKind::Other);
|
||||
|
||||
assert!(test_ensure_eq("foo", "foo").is_ok());
|
||||
|
||||
let err = test_ensure_ne("foo", "foo").unwrap_err();
|
||||
assert_eq!(format!("{err}"), "predicate failed");
|
||||
assert_eq!(err.kind(), &ErrorKind::Other);
|
||||
|
||||
assert!(test_ensure_ne("foo", "bar").is_ok());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
pub use typespec_client_core::http::headers::*;
|
||||
|
||||
// HTTP headers are case-insensitive.
|
||||
// We use lowercase below for simple comparisons downstream.
|
||||
|
||||
pub const ACCOUNT_KIND: HeaderName = HeaderName::from_static("x-ms-account-kind");
|
||||
pub const ACL: HeaderName = HeaderName::from_static("x-ms-acl");
|
||||
pub const ACTIVITY_ID: HeaderName = HeaderName::from_static("x-ms-activity-id");
|
||||
pub const APP: HeaderName = HeaderName::from_static("x-ms-app");
|
||||
pub const APPEND_POSITION: HeaderName = HeaderName::from_static("x-ms-blob-condition-appendpos"); // cspell:ignore appendpos
|
||||
pub const AZURE_ASYNCOPERATION: HeaderName = HeaderName::from_static("azure-asyncoperation");
|
||||
pub const BLOB_ACCESS_TIER: HeaderName = HeaderName::from_static("x-ms-access-tier");
|
||||
pub const BLOB_CACHE_CONTROL: HeaderName = HeaderName::from_static("x-ms-blob-cache-control");
|
||||
pub const BLOB_COMMITTED_BLOCK_COUNT: HeaderName =
|
||||
HeaderName::from_static("x-ms-blob-committed-block-count");
|
||||
pub const BLOB_CONTENT_LENGTH: HeaderName = HeaderName::from_static("x-ms-blob-content-length");
|
||||
pub const BLOB_PUBLIC_ACCESS: HeaderName = HeaderName::from_static("x-ms-blob-public-access");
|
||||
pub const BLOB_SEQUENCE_NUMBER: HeaderName = HeaderName::from_static("x-ms-blob-sequence-number");
|
||||
pub const BLOB_TYPE: HeaderName = HeaderName::from_static("x-ms-blob-type");
|
||||
pub const CLIENT_REQUEST_ID: HeaderName = HeaderName::from_static("x-ms-client-request-id");
|
||||
pub const CLIENT_VERSION: HeaderName = HeaderName::from_static("x-ms-client-version");
|
||||
pub const CONTENT_DISPOSITION: HeaderName =
|
||||
HeaderName::from_static("x-ms-blob-content-disposition");
|
||||
pub const CONTINUATION: HeaderName = HeaderName::from_static("x-ms-continuation");
|
||||
pub const COPY_COMPLETION_TIME: HeaderName = HeaderName::from_static("x-ms-copy-completion-time");
|
||||
pub const COPY_PROGRESS: HeaderName = HeaderName::from_static("x-ms-copy-progress");
|
||||
pub const COPY_SOURCE: HeaderName = HeaderName::from_static("x-ms-copy-source");
|
||||
pub const COPY_STATUS_DESCRIPTION: HeaderName =
|
||||
HeaderName::from_static("x-ms-copy-status-description");
|
||||
pub const COPY_STATUS: HeaderName = HeaderName::from_static("x-ms-copy-status");
|
||||
pub const CREATION_TIME: HeaderName = HeaderName::from_static("x-ms-creation-time");
|
||||
pub const DELETE_SNAPSHOTS: HeaderName = HeaderName::from_static("x-ms-delete-snapshots");
|
||||
pub const DELETE_TYPE_PERMANENT: HeaderName = HeaderName::from_static("x-ms-delete-type-permanent");
|
||||
pub const ENCRYPTION_ALGORITHM: HeaderName = HeaderName::from_static("x-ms-encryption-algorithm");
|
||||
pub const ENCRYPTION_KEY_SHA256: HeaderName = HeaderName::from_static("x-ms-encryption-key-sha256");
|
||||
pub const ENCRYPTION_KEY: HeaderName = HeaderName::from_static("x-ms-encryption-key");
|
||||
pub const HAS_IMMUTABILITY_POLICY: HeaderName =
|
||||
HeaderName::from_static("x-ms-has-immutability-policy");
|
||||
pub const HAS_LEGAL_HOLD: HeaderName = HeaderName::from_static("x-ms-has-legal-hold");
|
||||
pub const IF_SEQUENCE_NUMBER_EQ: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-eq");
|
||||
pub const IF_SEQUENCE_NUMBER_LE: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-le");
|
||||
pub const IF_SEQUENCE_NUMBER_LT: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-lt");
|
||||
pub const IF_TAGS: HeaderName = HeaderName::from_static("x-ms-if-tags");
|
||||
pub const ITEM_COUNT: HeaderName = HeaderName::from_static("x-ms-item-count");
|
||||
pub const ITEM_TYPE: HeaderName = HeaderName::from_static("x-ms-item-type");
|
||||
pub const LEASE_ACTION: HeaderName = HeaderName::from_static("x-ms-lease-action");
|
||||
pub const LEASE_BREAK_PERIOD: HeaderName = HeaderName::from_static("x-ms-lease-break-period");
|
||||
pub const LEASE_DURATION: HeaderName = HeaderName::from_static("x-ms-lease-duration");
|
||||
pub const LEASE_ID: HeaderName = HeaderName::from_static("x-ms-lease-id");
|
||||
pub const LEASE_STATE: HeaderName = HeaderName::from_static("x-ms-lease-state");
|
||||
pub const LEASE_STATUS: HeaderName = HeaderName::from_static("x-ms-lease-status");
|
||||
pub const LEASE_TIME: HeaderName = HeaderName::from_static("x-ms-lease-time");
|
||||
pub const MAX_ITEM_COUNT: HeaderName = HeaderName::from_static("x-ms-max-item-count");
|
||||
pub const META_PREFIX: HeaderName = HeaderName::from_static("x-ms-meta-");
|
||||
pub const MS_DATE: HeaderName = HeaderName::from_static("x-ms-date");
|
||||
pub const MS_RANGE: HeaderName = HeaderName::from_static("x-ms-range");
|
||||
pub const NAMESPACE_ENABLED: HeaderName = HeaderName::from_static("x-ms-namespace-enabled");
|
||||
pub const PAGE_WRITE: HeaderName = HeaderName::from_static("x-ms-page-write");
|
||||
pub const PROPERTIES: HeaderName = HeaderName::from_static("x-ms-properties");
|
||||
pub const PROPOSED_LEASE_ID: HeaderName = HeaderName::from_static("x-ms-proposed-lease-id");
|
||||
pub const RANGE_GET_CONTENT_CRC64: HeaderName =
|
||||
HeaderName::from_static("x-ms-range-get-content-crc64");
|
||||
pub const RANGE_GET_CONTENT_MD5: HeaderName = HeaderName::from_static("x-ms-range-get-content-md5");
|
||||
pub const REQUEST_ID: HeaderName = HeaderName::from_static("x-ms-request-id");
|
||||
pub const REQUEST_SERVER_ENCRYPTED: HeaderName =
|
||||
HeaderName::from_static("x-ms-request-server-encrypted");
|
||||
pub const REQUIRES_SYNC: HeaderName = HeaderName::from_static("x-ms-requires-sync");
|
||||
pub const SERVER_ENCRYPTED: HeaderName = HeaderName::from_static("x-ms-server-encrypted");
|
||||
pub const SESSION_TOKEN: HeaderName = HeaderName::from_static("x-ms-session-token");
|
||||
pub const SKU_NAME: HeaderName = HeaderName::from_static("x-ms-sku-name");
|
||||
pub const SOURCE_IF_MATCH: HeaderName = HeaderName::from_static("x-ms-source-if-match");
|
||||
pub const SOURCE_IF_MODIFIED_SINCE: HeaderName =
|
||||
HeaderName::from_static("x-ms-source-if-modified-since");
|
||||
pub const SOURCE_IF_NONE_MATCH: HeaderName = HeaderName::from_static("x-ms-source-if-none-match");
|
||||
pub const SOURCE_IF_UNMODIFIED_SINCE: HeaderName =
|
||||
HeaderName::from_static("x-ms-source-if-unmodified-since");
|
||||
pub const SOURCE_LEASE_ID: HeaderName = HeaderName::from_static("x-ms-source-lease-id");
|
||||
pub const SOURCE_RANGE: HeaderName = HeaderName::from_static("x-ms-source-range");
|
||||
pub const TAGS: HeaderName = HeaderName::from_static("x-ms-tags");
|
||||
pub const USER: HeaderName = HeaderName::from_static("x-ms-user");
|
||||
pub const VERSION: HeaderName = HeaderName::from_static("x-ms-version");
|
||||
pub const X_MS_RETRY_AFTER_MS: HeaderName = HeaderName::from_static("x-ms-retry-after-ms");
|
|
@ -1,374 +0,0 @@
|
|||
//! Azure HTTP headers.
|
||||
mod utilities;
|
||||
|
||||
use crate::error::{Error, ErrorKind, ResultExt};
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
pub use utilities::*;
|
||||
|
||||
/// A trait for converting a type into request headers
|
||||
pub trait AsHeaders {
|
||||
type Iter: Iterator<Item = (HeaderName, HeaderValue)>;
|
||||
fn as_headers(&self) -> Self::Iter;
|
||||
}
|
||||
|
||||
impl<T> AsHeaders for T
|
||||
where
|
||||
T: Header,
|
||||
{
|
||||
type Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>;
|
||||
|
||||
fn as_headers(&self) -> Self::Iter {
|
||||
vec![(self.name(), self.value())].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsHeaders for Option<T>
|
||||
where
|
||||
T: AsHeaders<Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>>,
|
||||
{
|
||||
type Iter = T::Iter;
|
||||
|
||||
fn as_headers(&self) -> Self::Iter {
|
||||
match self {
|
||||
Some(h) => h.as_headers(),
|
||||
None => vec![].into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// View a type as an HTTP header.
|
||||
///
|
||||
/// Ad interim there are two default functions: `add_to_builder` and `add_to_request`.
|
||||
///
|
||||
/// While not restricted by the type system, please add HTTP headers only. In particular, do not
|
||||
/// interact with the body of the request.
|
||||
///
|
||||
/// As soon as the migration to the pipeline architecture will be complete we will phase out
|
||||
/// `add_to_builder`.
|
||||
pub trait Header {
|
||||
fn name(&self) -> HeaderName;
|
||||
fn value(&self) -> HeaderValue;
|
||||
}
|
||||
|
||||
/// A collection of headers
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub struct Headers(std::collections::HashMap<HeaderName, HeaderValue>);
|
||||
|
||||
impl Headers {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Optionally get a header value as a String
|
||||
pub fn get_optional_string(&self, key: &HeaderName) -> Option<String> {
|
||||
self.get_as(key).ok()
|
||||
}
|
||||
|
||||
/// Get a header value as a str or error if it is not found
|
||||
pub fn get_str(&self, key: &HeaderName) -> crate::Result<&str> {
|
||||
self.get_with(key, |s| crate::Result::Ok(s.as_str()))
|
||||
}
|
||||
|
||||
/// Optionally get a header value as a str
|
||||
pub fn get_optional_str(&self, key: &HeaderName) -> Option<&str> {
|
||||
self.get_str(key).ok()
|
||||
}
|
||||
|
||||
/// Get a header value parsing it as the type or error if it's not found or it fails to parse
|
||||
pub fn get_as<V, E>(&self, key: &HeaderName) -> crate::Result<V>
|
||||
where
|
||||
V: FromStr<Err = E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.get_with(key, |s| s.as_str().parse())
|
||||
}
|
||||
|
||||
/// Optionally get a header value parsing it as the type or error if it fails to parse
|
||||
pub fn get_optional_as<V, E>(&self, key: &HeaderName) -> crate::Result<Option<V>>
|
||||
where
|
||||
V: FromStr<Err = E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.get_optional_with(key, |s| s.as_str().parse())
|
||||
}
|
||||
|
||||
/// Get a header value using the parser or error if it is not found or fails to parse
|
||||
pub fn get_with<'a, V, F, E>(&'a self, key: &HeaderName, parser: F) -> crate::Result<V>
|
||||
where
|
||||
F: FnOnce(&'a HeaderValue) -> Result<V, E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.get_optional_with(key, parser)?.ok_or_else(|| {
|
||||
Error::with_message(ErrorKind::DataConversion, || {
|
||||
format!("header not found {}", key.as_str())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Optionally get a header value using the parser or error if it fails to parse
|
||||
pub fn get_optional_with<'a, V, F, E>(
|
||||
&'a self,
|
||||
key: &HeaderName,
|
||||
parser: F,
|
||||
) -> crate::Result<Option<V>>
|
||||
where
|
||||
F: FnOnce(&'a HeaderValue) -> Result<V, E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.0
|
||||
.get(key)
|
||||
.map(|v: &HeaderValue| {
|
||||
parser(v).with_context(ErrorKind::DataConversion, || {
|
||||
let ty = std::any::type_name::<V>();
|
||||
format!("unable to parse header '{key:?}: {v:?}' into {ty}",)
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Insert a header name/value pair
|
||||
pub fn insert<K, V>(&mut self, key: K, value: V)
|
||||
where
|
||||
K: Into<HeaderName>,
|
||||
V: Into<HeaderValue>,
|
||||
{
|
||||
self.0.insert(key.into(), value.into());
|
||||
}
|
||||
|
||||
/// Add headers to the headers collection
|
||||
pub fn add<H>(&mut self, header: H)
|
||||
where
|
||||
H: AsHeaders,
|
||||
{
|
||||
for (key, value) in header.as_headers() {
|
||||
self.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all the header name/value pairs
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&HeaderName, &HeaderValue)> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Headers {
|
||||
type Item = (HeaderName, HeaderValue);
|
||||
|
||||
type IntoIter = std::collections::hash_map::IntoIter<HeaderName, HeaderValue>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::collections::HashMap<HeaderName, HeaderValue>> for Headers {
|
||||
fn from(c: std::collections::HashMap<HeaderName, HeaderValue>) -> Self {
|
||||
Self(c)
|
||||
}
|
||||
}
|
||||
|
||||
/// A header name
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct HeaderName(std::borrow::Cow<'static, str>);
|
||||
|
||||
impl HeaderName {
|
||||
pub const fn from_static(s: &'static str) -> Self {
|
||||
ensure_no_uppercase(s);
|
||||
Self(std::borrow::Cow::Borrowed(s))
|
||||
}
|
||||
|
||||
fn from_cow<C>(c: C) -> Self
|
||||
where
|
||||
C: Into<std::borrow::Cow<'static, str>>,
|
||||
{
|
||||
let c = c.into();
|
||||
assert!(
|
||||
c.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()),
|
||||
"header names must be lowercase: {c}"
|
||||
);
|
||||
Self(c)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures the supplied string does not contain any uppercase ascii characters
|
||||
const fn ensure_no_uppercase(s: &str) {
|
||||
let bytes = s.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
let byte = bytes[i];
|
||||
assert!(
|
||||
!(byte >= 65u8 && byte <= 90u8),
|
||||
"header names must not contain uppercase letters"
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for HeaderName {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Self::from_cow(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for HeaderName {
|
||||
fn from(s: String) -> Self {
|
||||
Self::from_cow(s.to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
/// A header value
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct HeaderValue(std::borrow::Cow<'static, str>);
|
||||
|
||||
impl HeaderValue {
|
||||
pub const fn from_static(s: &'static str) -> Self {
|
||||
Self(std::borrow::Cow::Borrowed(s))
|
||||
}
|
||||
|
||||
pub fn from_cow<C>(c: C) -> Self
|
||||
where
|
||||
C: Into<std::borrow::Cow<'static, str>>,
|
||||
{
|
||||
Self(c.into())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for HeaderValue {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Self::from_cow(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for HeaderValue {
|
||||
fn from(s: String) -> Self {
|
||||
Self::from_cow(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for HeaderValue {
|
||||
fn from(s: &String) -> Self {
|
||||
s.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
// headers are case insensitive
|
||||
// we are using all lowercase values
|
||||
// same as https://github.com/hyperium/http/blob/master/util/src/main.rs
|
||||
|
||||
pub const ACCEPT: HeaderName = HeaderName::from_static("accept");
|
||||
pub const ACCEPT_ENCODING: HeaderName = HeaderName::from_static("accept-encoding");
|
||||
pub const ACL: HeaderName = HeaderName::from_static("x-ms-acl");
|
||||
pub const ACCOUNT_KIND: HeaderName = HeaderName::from_static("x-ms-account-kind");
|
||||
pub const ACTIVITY_ID: HeaderName = HeaderName::from_static("x-ms-activity-id");
|
||||
pub const APP: HeaderName = HeaderName::from_static("x-ms-app");
|
||||
pub const AUTHORIZATION: HeaderName = HeaderName::from_static("authorization");
|
||||
pub const APPEND_POSITION: HeaderName = HeaderName::from_static("x-ms-blob-condition-appendpos"); // cspell:ignore appendpos
|
||||
pub const BLOB_ACCESS_TIER: HeaderName = HeaderName::from_static("x-ms-access-tier");
|
||||
pub const BLOB_CONTENT_LENGTH: HeaderName = HeaderName::from_static("x-ms-blob-content-length");
|
||||
pub const BLOB_PUBLIC_ACCESS: HeaderName = HeaderName::from_static("x-ms-blob-public-access");
|
||||
pub const BLOB_SEQUENCE_NUMBER: HeaderName = HeaderName::from_static("x-ms-blob-sequence-number");
|
||||
pub const BLOB_TYPE: HeaderName = HeaderName::from_static("x-ms-blob-type");
|
||||
pub const BLOB_CACHE_CONTROL: HeaderName = HeaderName::from_static("x-ms-blob-cache-control");
|
||||
pub const CACHE_CONTROL: HeaderName = HeaderName::from_static("cache-control");
|
||||
pub const CLIENT_REQUEST_ID: HeaderName = HeaderName::from_static("x-ms-client-request-id");
|
||||
pub const CLIENT_VERSION: HeaderName = HeaderName::from_static("x-ms-client-version");
|
||||
pub const CONTENT_DISPOSITION: HeaderName =
|
||||
HeaderName::from_static("x-ms-blob-content-disposition");
|
||||
pub const CONTENT_ENCODING: HeaderName = HeaderName::from_static("content-encoding");
|
||||
pub const CONTENT_LANGUAGE: HeaderName = HeaderName::from_static("content-language");
|
||||
pub const CONTENT_LENGTH: HeaderName = HeaderName::from_static("content-length");
|
||||
pub const CONTENT_LOCATION: HeaderName = HeaderName::from_static("content-location");
|
||||
pub const CONTENT_MD5: HeaderName = HeaderName::from_static("content-md5");
|
||||
pub const CONTENT_RANGE: HeaderName = HeaderName::from_static("content-range");
|
||||
pub const CONTENT_SECURITY_POLICY: HeaderName = HeaderName::from_static("content-security-policy");
|
||||
pub const CONTENT_TYPE: HeaderName = HeaderName::from_static("content-type");
|
||||
pub const CONTINUATION: HeaderName = HeaderName::from_static("x-ms-continuation");
|
||||
pub const COPY_COMPLETION_TIME: HeaderName = HeaderName::from_static("x-ms-copy-completion-time");
|
||||
pub const COPY_PROGRESS: HeaderName = HeaderName::from_static("x-ms-copy-progress");
|
||||
pub const COPY_SOURCE: HeaderName = HeaderName::from_static("x-ms-copy-source");
|
||||
pub const COPY_STATUS: HeaderName = HeaderName::from_static("x-ms-copy-status");
|
||||
pub const COPY_STATUS_DESCRIPTION: HeaderName =
|
||||
HeaderName::from_static("x-ms-copy-status-description");
|
||||
pub const CREATION_TIME: HeaderName = HeaderName::from_static("x-ms-creation-time");
|
||||
pub const DATE: HeaderName = HeaderName::from_static("date");
|
||||
pub const DELETE_SNAPSHOTS: HeaderName = HeaderName::from_static("x-ms-delete-snapshots");
|
||||
pub const DELETE_TYPE_PERMANENT: HeaderName = HeaderName::from_static("x-ms-delete-type-permanent");
|
||||
pub const ETAG: HeaderName = HeaderName::from_static("etag");
|
||||
pub const ERROR_CODE: HeaderName = HeaderName::from_static("x-ms-error-code");
|
||||
pub const HAS_IMMUTABILITY_POLICY: HeaderName =
|
||||
HeaderName::from_static("x-ms-has-immutability-policy");
|
||||
pub const HAS_LEGAL_HOLD: HeaderName = HeaderName::from_static("x-ms-has-legal-hold");
|
||||
pub const IF_MATCH: HeaderName = HeaderName::from_static("if-match");
|
||||
pub const IF_MODIFIED_SINCE: HeaderName = HeaderName::from_static("if-modified-since");
|
||||
pub const IF_NONE_MATCH: HeaderName = HeaderName::from_static("if-none-match");
|
||||
pub const IF_RANGE: HeaderName = HeaderName::from_static("if-range");
|
||||
pub const IF_UNMODIFIED_SINCE: HeaderName = HeaderName::from_static("if-unmodified-since");
|
||||
pub const IF_SEQUENCE_NUMBER_EQ: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-eq");
|
||||
pub const IF_SEQUENCE_NUMBER_LE: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-le");
|
||||
pub const IF_SEQUENCE_NUMBER_LT: HeaderName = HeaderName::from_static("x-ms-if-sequence-number-lt");
|
||||
pub const IF_TAGS: HeaderName = HeaderName::from_static("x-ms-if-tags");
|
||||
pub const ITEM_COUNT: HeaderName = HeaderName::from_static("x-ms-item-count");
|
||||
pub const ITEM_TYPE: HeaderName = HeaderName::from_static("x-ms-item-type");
|
||||
pub const KEEP_ALIVE: HeaderName = HeaderName::from_static("keep-alive");
|
||||
pub const LAST_MODIFIED: HeaderName = HeaderName::from_static("last-modified");
|
||||
pub const LEASE_ACTION: HeaderName = HeaderName::from_static("x-ms-lease-action");
|
||||
pub const LEASE_BREAK_PERIOD: HeaderName = HeaderName::from_static("x-ms-lease-break-period");
|
||||
pub const LEASE_DURATION: HeaderName = HeaderName::from_static("x-ms-lease-duration");
|
||||
pub const LEASE_ID: HeaderName = HeaderName::from_static("x-ms-lease-id");
|
||||
pub const LEASE_STATE: HeaderName = HeaderName::from_static("x-ms-lease-state");
|
||||
pub const LEASE_STATUS: HeaderName = HeaderName::from_static("x-ms-lease-status");
|
||||
pub const LEASE_TIME: HeaderName = HeaderName::from_static("x-ms-lease-time");
|
||||
pub const LINK: HeaderName = HeaderName::from_static("link");
|
||||
pub const LOCATION: HeaderName = HeaderName::from_static("location");
|
||||
pub const MAX_ITEM_COUNT: HeaderName = HeaderName::from_static("x-ms-max-item-count");
|
||||
pub const META_PREFIX: HeaderName = HeaderName::from_static("x-ms-meta-");
|
||||
pub const MS_DATE: HeaderName = HeaderName::from_static("x-ms-date");
|
||||
pub const MS_RANGE: HeaderName = HeaderName::from_static("x-ms-range");
|
||||
pub const NAMESPACE_ENABLED: HeaderName = HeaderName::from_static("x-ms-namespace-enabled");
|
||||
pub const PAGE_WRITE: HeaderName = HeaderName::from_static("x-ms-page-write");
|
||||
pub const PROPERTIES: HeaderName = HeaderName::from_static("x-ms-properties");
|
||||
pub const PREFER: HeaderName = HeaderName::from_static("prefer");
|
||||
pub const PROPOSED_LEASE_ID: HeaderName = HeaderName::from_static("x-ms-proposed-lease-id");
|
||||
pub const RANGE: HeaderName = HeaderName::from_static("range");
|
||||
pub const RANGE_GET_CONTENT_CRC64: HeaderName =
|
||||
HeaderName::from_static("x-ms-range-get-content-crc64");
|
||||
pub const RANGE_GET_CONTENT_MD5: HeaderName = HeaderName::from_static("x-ms-range-get-content-md5");
|
||||
pub const REQUEST_ID: HeaderName = HeaderName::from_static("x-ms-request-id");
|
||||
pub const REQUEST_SERVER_ENCRYPTED: HeaderName =
|
||||
HeaderName::from_static("x-ms-request-server-encrypted");
|
||||
pub const REQUIRES_SYNC: HeaderName = HeaderName::from_static("x-ms-requires-sync");
|
||||
pub const RETRY_AFTER: HeaderName = HeaderName::from_static("retry-after");
|
||||
pub const RETRY_AFTER_MS: HeaderName = HeaderName::from_static("retry-after-ms");
|
||||
pub const X_MS_RETRY_AFTER_MS: HeaderName = HeaderName::from_static("x-ms-retry-after-ms");
|
||||
pub const SERVER: HeaderName = HeaderName::from_static("server");
|
||||
pub const SERVER_ENCRYPTED: HeaderName = HeaderName::from_static("x-ms-server-encrypted");
|
||||
pub const SESSION_TOKEN: HeaderName = HeaderName::from_static("x-ms-session-token");
|
||||
pub const SKU_NAME: HeaderName = HeaderName::from_static("x-ms-sku-name");
|
||||
pub const SOURCE_IF_MATCH: HeaderName = HeaderName::from_static("x-ms-source-if-match");
|
||||
pub const SOURCE_IF_MODIFIED_SINCE: HeaderName =
|
||||
HeaderName::from_static("x-ms-source-if-modified-since");
|
||||
pub const SOURCE_IF_NONE_MATCH: HeaderName = HeaderName::from_static("x-ms-source-if-none-match");
|
||||
pub const SOURCE_IF_UNMODIFIED_SINCE: HeaderName =
|
||||
HeaderName::from_static("x-ms-source-if-unmodified-since");
|
||||
pub const SOURCE_LEASE_ID: HeaderName = HeaderName::from_static("x-ms-source-lease-id");
|
||||
pub const TAGS: HeaderName = HeaderName::from_static("x-ms-tags");
|
||||
pub const USER: HeaderName = HeaderName::from_static("x-ms-user");
|
||||
pub const USER_AGENT: HeaderName = HeaderName::from_static("user-agent");
|
||||
pub const VERSION: HeaderName = HeaderName::from_static("x-ms-version");
|
||||
pub const WWW_AUTHENTICATE: HeaderName = HeaderName::from_static("www-authenticate");
|
||||
pub const ENCRYPTION_ALGORITHM: HeaderName = HeaderName::from_static("x-ms-encryption-algorithm");
|
||||
pub const ENCRYPTION_KEY: HeaderName = HeaderName::from_static("x-ms-encryption-key");
|
||||
pub const ENCRYPTION_KEY_SHA256: HeaderName = HeaderName::from_static("x-ms-encryption-key-sha256");
|
||||
pub const BLOB_COMMITTED_BLOCK_COUNT: HeaderName =
|
||||
HeaderName::from_static("x-ms-blob-committed-block-count");
|
||||
pub const AZURE_ASYNCOPERATION: HeaderName = HeaderName::from_static("azure-asyncoperation");
|
||||
pub const OPERATION_LOCATION: HeaderName = HeaderName::from_static("operation-location");
|
||||
pub const SOURCE_RANGE: HeaderName = HeaderName::from_static("x-ms-source-range");
|
|
@ -1,114 +0,0 @@
|
|||
use super::*;
|
||||
use crate::prelude::Continuation;
|
||||
use crate::request_options::LeaseId;
|
||||
use crate::{date, RequestId, SessionToken};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn lease_id_from_headers(headers: &Headers) -> crate::Result<LeaseId> {
|
||||
headers.get_as(&LEASE_ID)
|
||||
}
|
||||
|
||||
pub fn request_id_from_headers(headers: &Headers) -> crate::Result<RequestId> {
|
||||
headers.get_as(&REQUEST_ID)
|
||||
}
|
||||
|
||||
pub fn client_request_id_from_headers_optional(headers: &Headers) -> Option<String> {
|
||||
headers.get_optional_string(&CLIENT_REQUEST_ID)
|
||||
}
|
||||
|
||||
pub fn last_modified_from_headers_optional(
|
||||
headers: &Headers,
|
||||
) -> crate::Result<Option<OffsetDateTime>> {
|
||||
headers
|
||||
.get_optional_str(&LAST_MODIFIED)
|
||||
.map(date::parse_rfc1123)
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn date_from_headers(headers: &Headers) -> crate::Result<OffsetDateTime> {
|
||||
rfc1123_from_headers_mandatory(headers, &DATE)
|
||||
}
|
||||
|
||||
pub fn last_modified_from_headers(headers: &Headers) -> crate::Result<OffsetDateTime> {
|
||||
rfc1123_from_headers_mandatory(headers, &LAST_MODIFIED)
|
||||
}
|
||||
|
||||
pub fn rfc1123_from_headers_mandatory(
|
||||
headers: &Headers,
|
||||
header_name: &HeaderName,
|
||||
) -> crate::Result<OffsetDateTime> {
|
||||
let date = headers.get_str(header_name)?;
|
||||
date::parse_rfc1123(date)
|
||||
}
|
||||
|
||||
pub fn continuation_token_from_headers_optional(
|
||||
headers: &Headers,
|
||||
) -> crate::Result<Option<Continuation>> {
|
||||
Ok(headers
|
||||
.get_optional_string(&CONTINUATION)
|
||||
.map(Continuation::from))
|
||||
}
|
||||
|
||||
pub fn sku_name_from_headers(headers: &Headers) -> crate::Result<String> {
|
||||
headers.get_as(&SKU_NAME)
|
||||
}
|
||||
|
||||
pub fn account_kind_from_headers(headers: &Headers) -> crate::Result<String> {
|
||||
headers.get_as(&ACCOUNT_KIND)
|
||||
}
|
||||
|
||||
pub fn etag_from_headers_optional(headers: &Headers) -> crate::Result<Option<String>> {
|
||||
Ok(headers.get_optional_string(&ETAG))
|
||||
}
|
||||
|
||||
pub fn etag_from_headers(headers: &Headers) -> crate::Result<String> {
|
||||
headers.get_as(&ETAG)
|
||||
}
|
||||
|
||||
pub fn lease_time_from_headers(headers: &Headers) -> crate::Result<u8> {
|
||||
headers.get_as(&LEASE_TIME)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "azurite_workaround"))]
|
||||
pub fn delete_type_permanent_from_headers(headers: &Headers) -> crate::Result<bool> {
|
||||
headers.get_as(&DELETE_TYPE_PERMANENT)
|
||||
}
|
||||
|
||||
#[cfg(feature = "azurite_workaround")]
|
||||
pub fn delete_type_permanent_from_headers(headers: &Headers) -> crate::Result<bool> {
|
||||
let result = headers.get_as(&DELETE_TYPE_PERMANENT);
|
||||
if result.is_ok() {
|
||||
result
|
||||
} else {
|
||||
tracing::warn!("Error receiving delete type permanent. returning false");
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sequence_number_from_headers(headers: &Headers) -> crate::Result<u64> {
|
||||
headers.get_as(&BLOB_SEQUENCE_NUMBER)
|
||||
}
|
||||
|
||||
pub fn session_token_from_headers(headers: &Headers) -> crate::Result<SessionToken> {
|
||||
headers.get_as(&SESSION_TOKEN)
|
||||
}
|
||||
|
||||
pub fn server_from_headers(headers: &Headers) -> crate::Result<String> {
|
||||
headers.get_as(&SERVER)
|
||||
}
|
||||
|
||||
pub fn version_from_headers(headers: &Headers) -> crate::Result<String> {
|
||||
headers.get_as(&VERSION)
|
||||
}
|
||||
|
||||
pub fn request_server_encrypted_from_headers(headers: &Headers) -> crate::Result<bool> {
|
||||
headers.get_as(&REQUEST_SERVER_ENCRYPTED)
|
||||
}
|
||||
|
||||
pub fn content_type_from_headers(headers: &Headers) -> crate::Result<String> {
|
||||
headers.get_as(&CONTENT_TYPE)
|
||||
}
|
||||
|
||||
pub fn item_count_from_headers(headers: &Headers) -> crate::Result<u32> {
|
||||
headers.get_as(&ITEM_COUNT)
|
||||
}
|
|
@ -28,7 +28,6 @@ mod pageable;
|
|||
mod pipeline;
|
||||
mod policies;
|
||||
mod request;
|
||||
mod response;
|
||||
mod seekable_stream;
|
||||
|
||||
pub mod auth;
|
||||
|
@ -62,9 +61,11 @@ pub use pageable::*;
|
|||
pub use pipeline::Pipeline;
|
||||
pub use policies::*;
|
||||
pub use request::*;
|
||||
pub use response::*;
|
||||
pub use seekable_stream::*;
|
||||
pub use sleep::sleep;
|
||||
pub use typespec_client_core::http::{
|
||||
CollectedResponse, PinnedStream, RawResponse, Response, ResponseBody,
|
||||
};
|
||||
|
||||
// re-export important types at crate level
|
||||
pub use http_types::Method;
|
||||
|
|
|
@ -42,7 +42,7 @@ use azure_core::{
|
|||
error::{ErrorKind, ResultExt},
|
||||
headers, HttpClient, Request, Url,
|
||||
};
|
||||
use azure_core::{json::from_json, Method};
|
||||
use azure_core::{error::http_response_from_body, json::from_json, Method};
|
||||
use login_response::LoginResponse;
|
||||
use std::sync::Arc;
|
||||
use url::form_urlencoded;
|
||||
|
@ -79,7 +79,7 @@ pub async fn perform(
|
|||
let rsp_status = rsp.status();
|
||||
let rsp_body = rsp.into_body().collect().await?;
|
||||
if !rsp_status.is_success() {
|
||||
return Err(ErrorKind::http_response_from_body(rsp_status, &rsp_body).into_error());
|
||||
return Err(http_response_from_body(rsp_status, &rsp_body).into_error());
|
||||
}
|
||||
from_json(&rsp_body)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ mod device_code_responses;
|
|||
|
||||
use azure_core::{
|
||||
content_type,
|
||||
error::{Error, ErrorKind},
|
||||
error::{http_response_from_body, Error, ErrorKind},
|
||||
headers,
|
||||
json::from_json,
|
||||
sleep, HttpClient, Method, RawResponse, Request, Url,
|
||||
|
@ -41,7 +41,7 @@ where
|
|||
let rsp_status = rsp.status();
|
||||
let rsp_body = rsp.into_body().collect().await?;
|
||||
if !rsp_status.is_success() {
|
||||
return Err(ErrorKind::http_response_from_body(rsp_status, &rsp_body).into_error());
|
||||
return Err(http_response_from_body(rsp_status, &rsp_body).into_error());
|
||||
}
|
||||
let device_code_response: DeviceCodePhaseOneResponse = from_json(&rsp_body)?;
|
||||
|
||||
|
|
|
@ -31,13 +31,13 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! You can learn more about this authorization flow [here](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential).
|
||||
//! You can learn more about this authorization flow [here](https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential).
|
||||
|
||||
mod login_response;
|
||||
|
||||
use azure_core::{
|
||||
content_type,
|
||||
error::{ErrorKind, ResultExt},
|
||||
error::{http_response_from_body, ErrorKind, ResultExt},
|
||||
headers, HttpClient, Method, Request, Url,
|
||||
};
|
||||
use login_response::LoginResponse;
|
||||
|
@ -86,6 +86,6 @@ pub async fn perform(
|
|||
let rsp_body = rsp.into_body().collect().await?;
|
||||
let text = std::str::from_utf8(&rsp_body)?;
|
||||
error!("rsp_body == {:?}", text);
|
||||
Err(ErrorKind::http_response_from_body(rsp_status, &rsp_body).into_error())
|
||||
Err(http_response_from_body(rsp_status, &rsp_body).into_error())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use azure_core::{
|
|||
error::{Error, ErrorKind, ResultExt},
|
||||
headers, HttpClient, Request, Url,
|
||||
};
|
||||
use azure_core::{json::from_json, Method};
|
||||
use azure_core::{error::http_response_from_body, json::from_json, Method};
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
@ -51,8 +51,8 @@ pub async fn exchange(
|
|||
rsp.json().await.map_kind(ErrorKind::Credential)
|
||||
} else {
|
||||
let rsp_body = rsp.into_body().collect().await?;
|
||||
let token_error: RefreshTokenError = from_json(&rsp_body)
|
||||
.map_err(|_| ErrorKind::http_response_from_body(rsp_status, &rsp_body))?;
|
||||
let token_error: RefreshTokenError =
|
||||
from_json(&rsp_body).map_err(|_| http_response_from_body(rsp_status, &rsp_body))?;
|
||||
Err(Error::new(ErrorKind::Credential, token_error))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{token_credentials::cache::TokenCache, TokenCredentialOptions};
|
|||
use azure_core::{
|
||||
auth::{AccessToken, Secret, TokenCredential},
|
||||
base64, content_type,
|
||||
error::{Error, ErrorKind, ResultExt},
|
||||
error::{http_response_from_body, Error, ErrorKind, ResultExt},
|
||||
headers, HttpClient, Method, Request,
|
||||
};
|
||||
|
||||
|
@ -261,7 +261,7 @@ impl ClientCertificateCredential {
|
|||
|
||||
if !rsp_status.is_success() {
|
||||
let rsp_body = rsp.into_body().collect().await?;
|
||||
return Err(ErrorKind::http_response_from_body(rsp_status, &rsp_body).into_error());
|
||||
return Err(http_response_from_body(rsp_status, &rsp_body).into_error());
|
||||
}
|
||||
|
||||
let response: AadTokenResponse = rsp.json().await?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{token_credentials::cache::TokenCache, TokenCredentialOptions};
|
||||
use azure_core::{
|
||||
auth::{AccessToken, Secret, TokenCredential},
|
||||
error::{Error, ErrorKind},
|
||||
error::{http_response_from_body, Error, ErrorKind},
|
||||
headers::HeaderName,
|
||||
json::from_json,
|
||||
HttpClient, Method, Request, StatusCode, Url,
|
||||
|
@ -107,9 +107,7 @@ impl ImdsManagedIdentityCredential {
|
|||
))
|
||||
}
|
||||
rsp_status => {
|
||||
return Err(
|
||||
ErrorKind::http_response_from_body(rsp_status, &rsp_body).into_error()
|
||||
)
|
||||
return Err(http_response_from_body(rsp_status, &rsp_body).into_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,25 @@ name = "typespec"
|
|||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Project root for all TypeSpec-related crates"
|
||||
description = "Project root for all TypeSpec-related crates."
|
||||
homepage = "https://typespec.io"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
categories = [
|
||||
"compilers",
|
||||
"development-tools",
|
||||
]
|
||||
keywords = [
|
||||
"typespec",
|
||||
]
|
||||
categories = ["compilers", "development-tools"]
|
||||
keywords = ["typespec"]
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
http-types = { workspace = true, optional = true }
|
||||
quick-xml = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["http", "json"]
|
||||
http = ["dep:http-types"]
|
||||
json = ["dep:serde_json"]
|
||||
xml = ["dep:quick-xml"]
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::StatusCode;
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Interfaces for working with errors.
|
||||
|
||||
use http_types::StatusCode;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{Debug, Display};
|
||||
mod http_error;
|
||||
mod macros;
|
||||
pub use http_error::HttpError;
|
||||
|
||||
/// A convenience alias for `Result` where the error type is hard coded to [`Error`].
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
@ -14,6 +16,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
/// An HTTP status code that was not expected.
|
||||
#[cfg(feature = "http")]
|
||||
HttpResponse {
|
||||
status: StatusCode,
|
||||
error_code: Option<String>,
|
||||
|
@ -39,15 +42,10 @@ impl ErrorKind {
|
|||
}
|
||||
|
||||
/// Create an `ErrorKind` from an HTTP response.
|
||||
#[cfg(feature = "http")]
|
||||
pub fn http_response(status: StatusCode, error_code: Option<String>) -> Self {
|
||||
Self::HttpResponse { status, error_code }
|
||||
}
|
||||
|
||||
/// Create an `ErrorKind` from an HTTP response with response content.
|
||||
pub fn http_response_from_body(status: StatusCode, body: &[u8]) -> Self {
|
||||
let error_code = http_error::get_error_code_from_body(body);
|
||||
Self::HttpResponse { status, error_code }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ErrorKind {
|
||||
|
@ -70,7 +68,7 @@ impl Display for ErrorKind {
|
|||
}
|
||||
}
|
||||
|
||||
/// An error encountered when communicating with Azure.
|
||||
/// An error encountered when communicating with the service.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
context: Context,
|
||||
|
@ -202,20 +200,6 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
/// Cast this error as an [`HttpError`].
|
||||
///
|
||||
/// This searches the entire ["source" chain](https://doc.rust-lang.org/std/error/trait.Error.html#method.source)
|
||||
/// looking for an `HttpError`.
|
||||
pub fn as_http_error(&self) -> Option<&HttpError> {
|
||||
let mut error = self.get_ref()? as &(dyn std::error::Error);
|
||||
loop {
|
||||
match error.downcast_ref::<HttpError>() {
|
||||
Some(e) => return Some(e),
|
||||
None => error = error.source()?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner error, if any, downcast to the type provided.
|
||||
pub fn downcast_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
|
||||
self.get_ref()?.downcast_ref()
|
||||
|
@ -268,6 +252,7 @@ impl From<base64::DecodeError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
Self::new(ErrorKind::DataConversion, error)
|
||||
|
@ -470,33 +455,6 @@ mod tests {
|
|||
assert_eq!(format!("{inner}"), "second error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matching_against_http_error() {
|
||||
let kind = ErrorKind::http_response_from_body(StatusCode::ImATeapot, b"{}");
|
||||
|
||||
assert!(matches!(
|
||||
kind,
|
||||
ErrorKind::HttpResponse {
|
||||
status: StatusCode::ImATeapot,
|
||||
error_code: None
|
||||
}
|
||||
));
|
||||
|
||||
let kind = ErrorKind::http_response_from_body(
|
||||
StatusCode::ImATeapot,
|
||||
br#"{"error": {"code":"teapot"}}"#,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
kind,
|
||||
ErrorKind::HttpResponse {
|
||||
status: StatusCode::ImATeapot,
|
||||
error_code
|
||||
}
|
||||
if error_code.as_deref() == Some("teapot")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_result_kind() {
|
||||
let result = std::result::Result::<(), _>::Err(create_error());
|
|
@ -3,5 +3,4 @@
|
|||
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub const HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE");
|
||||
pub const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
pub mod error;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "typespec_client_core"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Client runtime for TypeSpec-generated libraries."
|
||||
homepage = "https://typespec.io"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
categories = ["compilers", "development-tools"]
|
||||
keywords = ["typespec"]
|
||||
|
||||
[dependencies]
|
||||
bytes = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
http-types = { workspace = true, optional = true }
|
||||
pin-project = { workspace = true }
|
||||
quick-xml = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
typespec = { workspace = true, default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["http", "json"]
|
||||
http = ["dep:http-types", "typespec/http"]
|
||||
json = ["typespec/json"]
|
||||
xml = ["dep:quick-xml", "typespec/xml"]
|
|
@ -0,0 +1,3 @@
|
|||
# TypeSpec Client Runtime
|
||||
|
||||
This is the runtime for [TypeSpec](https://typespec.io)-generated clients.
|
|
@ -1,4 +1,12 @@
|
|||
use crate::{headers, json::from_json, RawResponse, StatusCode};
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
use crate::json::from_json;
|
||||
use crate::{
|
||||
http::{headers, RawResponse, StatusCode},
|
||||
Error, ErrorKind,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
@ -37,6 +45,20 @@ impl HttpError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Try to create an HTTP error from an [`Error`].
|
||||
///
|
||||
/// This searches the entire ["source" chain](https://doc.rust-lang.org/std/error/trait.Error.html#method.source)
|
||||
/// looking for an `HttpError`.
|
||||
pub fn try_from(error: &Error) -> Option<&Self> {
|
||||
let mut error = error.get_ref()? as &(dyn std::error::Error);
|
||||
loop {
|
||||
match error.downcast_ref::<Self>() {
|
||||
Some(e) => return Some(e),
|
||||
None => error = error.source()?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status code for the HTTP error.
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.status
|
||||
|
@ -99,7 +121,7 @@ impl ErrorDetails {
|
|||
|
||||
/// Gets the error code if it's present in the headers.
|
||||
///
|
||||
/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors).
|
||||
/// For more info, see [guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors).
|
||||
fn get_error_code_from_header(headers: &HashMap<String, String>) -> Option<String> {
|
||||
headers.get(headers::ERROR_CODE.as_str()).cloned()
|
||||
}
|
||||
|
@ -117,18 +139,54 @@ struct ErrorBody {
|
|||
code: Option<String>,
|
||||
}
|
||||
|
||||
/// Create an [`ErrorKind`] from an HTTP response with response content.
|
||||
pub fn http_response_from_body(status: StatusCode, body: &[u8]) -> ErrorKind {
|
||||
let error_code = get_error_code_from_body(body);
|
||||
ErrorKind::http_response(status, error_code)
|
||||
}
|
||||
|
||||
/// Gets the error code if it's present in the body.
|
||||
///
|
||||
/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
|
||||
pub(crate) fn get_error_code_from_body(body: &[u8]) -> Option<String> {
|
||||
/// For more info, see [guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
|
||||
fn get_error_code_from_body(body: &[u8]) -> Option<String> {
|
||||
let decoded: ErrorBody = from_json(body).ok()?;
|
||||
decoded.error.and_then(|e| e.code).or(decoded.code)
|
||||
}
|
||||
|
||||
/// Gets the error message if it's present in the body.
|
||||
///
|
||||
/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
|
||||
pub(crate) fn get_error_message_from_body(body: &[u8]) -> Option<String> {
|
||||
/// For more info, see [guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
|
||||
fn get_error_message_from_body(body: &[u8]) -> Option<String> {
|
||||
let decoded: ErrorBody = from_json(body).ok()?;
|
||||
decoded.error.and_then(|e| e.message).or(decoded.message)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn matching_against_http_error() {
|
||||
let kind = http_response_from_body(StatusCode::ImATeapot, b"{}");
|
||||
|
||||
assert!(matches!(
|
||||
kind,
|
||||
ErrorKind::HttpResponse {
|
||||
status: StatusCode::ImATeapot,
|
||||
error_code: None
|
||||
}
|
||||
));
|
||||
|
||||
let kind =
|
||||
http_response_from_body(StatusCode::ImATeapot, br#"{"error": {"code":"teapot"}}"#);
|
||||
|
||||
assert!(matches!(
|
||||
kind,
|
||||
ErrorKind::HttpResponse {
|
||||
status: StatusCode::ImATeapot,
|
||||
error_code
|
||||
}
|
||||
if error_code.as_deref() == Some("teapot")
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod http_error;
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
pub use http_error::*;
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use super::*;
|
||||
|
||||
// HTTP headers are case-insensitive.
|
||||
// We use lowercase below for simple comparisons downstream.
|
||||
|
||||
pub const ACCEPT_ENCODING: HeaderName = HeaderName::from_static("accept-encoding");
|
||||
pub const ACCEPT: HeaderName = HeaderName::from_static("accept");
|
||||
pub const AUTHORIZATION: HeaderName = HeaderName::from_static("authorization");
|
||||
pub const CACHE_CONTROL: HeaderName = HeaderName::from_static("cache-control");
|
||||
pub const CONTENT_ENCODING: HeaderName = HeaderName::from_static("content-encoding");
|
||||
pub const CONTENT_LANGUAGE: HeaderName = HeaderName::from_static("content-language");
|
||||
pub const CONTENT_LENGTH: HeaderName = HeaderName::from_static("content-length");
|
||||
pub const CONTENT_LOCATION: HeaderName = HeaderName::from_static("content-location");
|
||||
pub const CONTENT_MD5: HeaderName = HeaderName::from_static("content-md5");
|
||||
pub const CONTENT_RANGE: HeaderName = HeaderName::from_static("content-range");
|
||||
pub const CONTENT_SECURITY_POLICY: HeaderName = HeaderName::from_static("content-security-policy");
|
||||
pub const CONTENT_TYPE: HeaderName = HeaderName::from_static("content-type");
|
||||
pub const DATE: HeaderName = HeaderName::from_static("date");
|
||||
pub const ETAG: HeaderName = HeaderName::from_static("etag");
|
||||
pub const IF_MATCH: HeaderName = HeaderName::from_static("if-match");
|
||||
pub const IF_MODIFIED_SINCE: HeaderName = HeaderName::from_static("if-modified-since");
|
||||
pub const IF_NONE_MATCH: HeaderName = HeaderName::from_static("if-none-match");
|
||||
pub const IF_RANGE: HeaderName = HeaderName::from_static("if-range");
|
||||
pub const IF_UNMODIFIED_SINCE: HeaderName = HeaderName::from_static("if-unmodified-since");
|
||||
pub const KEEP_ALIVE: HeaderName = HeaderName::from_static("keep-alive");
|
||||
pub const LAST_MODIFIED: HeaderName = HeaderName::from_static("last-modified");
|
||||
pub const LINK: HeaderName = HeaderName::from_static("link");
|
||||
pub const LOCATION: HeaderName = HeaderName::from_static("location");
|
||||
pub const OPERATION_LOCATION: HeaderName = HeaderName::from_static("operation-location");
|
||||
pub const PREFER: HeaderName = HeaderName::from_static("prefer");
|
||||
pub const RANGE: HeaderName = HeaderName::from_static("range");
|
||||
pub const RETRY_AFTER_MS: HeaderName = HeaderName::from_static("retry-after-ms");
|
||||
pub const RETRY_AFTER: HeaderName = HeaderName::from_static("retry-after");
|
||||
pub const SERVER: HeaderName = HeaderName::from_static("server");
|
||||
pub const USER_AGENT: HeaderName = HeaderName::from_static("user-agent");
|
||||
pub const WWW_AUTHENTICATE: HeaderName = HeaderName::from_static("www-authenticate");
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub const ERROR_CODE: HeaderName = HeaderName::from_static("x-ms-error-code");
|
|
@ -0,0 +1,275 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! HTTP headers.
|
||||
|
||||
mod common;
|
||||
mod microsoft;
|
||||
|
||||
pub use common::*;
|
||||
pub use microsoft::*;
|
||||
|
||||
use std::{borrow::Cow, fmt::Debug, str::FromStr};
|
||||
use typespec::error::{Error, ErrorKind, ResultExt};
|
||||
|
||||
/// A trait for converting a type into request headers.
|
||||
pub trait AsHeaders {
|
||||
type Iter: Iterator<Item = (HeaderName, HeaderValue)>;
|
||||
fn as_headers(&self) -> Self::Iter;
|
||||
}
|
||||
|
||||
impl<T> AsHeaders for T
|
||||
where
|
||||
T: Header,
|
||||
{
|
||||
type Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>;
|
||||
|
||||
/// Iterate over all the header name/value pairs.
|
||||
fn as_headers(&self) -> Self::Iter {
|
||||
vec![(self.name(), self.value())].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsHeaders for Option<T>
|
||||
where
|
||||
T: AsHeaders<Iter = std::vec::IntoIter<(HeaderName, HeaderValue)>>,
|
||||
{
|
||||
type Iter = T::Iter;
|
||||
|
||||
/// Iterate over all the header name/value pairs.
|
||||
fn as_headers(&self) -> Self::Iter {
|
||||
match self {
|
||||
Some(h) => h.as_headers(),
|
||||
None => vec![].into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// View a type as an HTTP header.
|
||||
///
|
||||
// Ad interim there are two default functions: `add_to_builder` and `add_to_request`.
|
||||
//
|
||||
// While not restricted by the type system, please add HTTP headers only. In particular, do not
|
||||
// interact with the body of the request.
|
||||
//
|
||||
// As soon as the migration to the pipeline architecture will be complete we will phase out
|
||||
// `add_to_builder`.
|
||||
pub trait Header {
|
||||
fn name(&self) -> HeaderName;
|
||||
fn value(&self) -> HeaderValue;
|
||||
}
|
||||
|
||||
/// A collection of headers.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub struct Headers(std::collections::HashMap<HeaderName, HeaderValue>);
|
||||
|
||||
impl Headers {
|
||||
/// Create a new headers collection.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Optionally get a header value as a `String`.
|
||||
pub fn get_optional_string(&self, key: &HeaderName) -> Option<String> {
|
||||
self.get_as(key).ok()
|
||||
}
|
||||
|
||||
/// Get a header value as a `str`, or err if it is not found.
|
||||
pub fn get_str(&self, key: &HeaderName) -> crate::Result<&str> {
|
||||
self.get_with(key, |s| crate::Result::Ok(s.as_str()))
|
||||
}
|
||||
|
||||
/// Optionally get a header value as a `str`.
|
||||
pub fn get_optional_str(&self, key: &HeaderName) -> Option<&str> {
|
||||
self.get_str(key).ok()
|
||||
}
|
||||
|
||||
/// Get a header value parsing it as the type, or err if it's not found or it fails to parse.
|
||||
pub fn get_as<V, E>(&self, key: &HeaderName) -> crate::Result<V>
|
||||
where
|
||||
V: FromStr<Err = E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.get_with(key, |s| s.as_str().parse())
|
||||
}
|
||||
|
||||
/// Optionally get a header value parsing it as the type, or err if it fails to parse.
|
||||
pub fn get_optional_as<V, E>(&self, key: &HeaderName) -> crate::Result<Option<V>>
|
||||
where
|
||||
V: FromStr<Err = E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.get_optional_with(key, |s| s.as_str().parse())
|
||||
}
|
||||
|
||||
/// Get a header value using the parser, or err if it is not found or fails to parse.
|
||||
pub fn get_with<'a, V, F, E>(&'a self, key: &HeaderName, parser: F) -> crate::Result<V>
|
||||
where
|
||||
F: FnOnce(&'a HeaderValue) -> Result<V, E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.get_optional_with(key, parser)?.ok_or_else(|| {
|
||||
Error::with_message(ErrorKind::DataConversion, || {
|
||||
format!("header not found {}", key.as_str())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Optionally get a header value using the parser, or err if it fails to parse.
|
||||
pub fn get_optional_with<'a, V, F, E>(
|
||||
&'a self,
|
||||
key: &HeaderName,
|
||||
parser: F,
|
||||
) -> crate::Result<Option<V>>
|
||||
where
|
||||
F: FnOnce(&'a HeaderValue) -> Result<V, E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.0
|
||||
.get(key)
|
||||
.map(|v: &HeaderValue| {
|
||||
parser(v).with_context(ErrorKind::DataConversion, || {
|
||||
let ty = std::any::type_name::<V>();
|
||||
format!("unable to parse header '{key:?}: {v:?}' into {ty}",)
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Insert a header name/value pair.
|
||||
pub fn insert<K, V>(&mut self, key: K, value: V)
|
||||
where
|
||||
K: Into<HeaderName>,
|
||||
V: Into<HeaderValue>,
|
||||
{
|
||||
self.0.insert(key.into(), value.into());
|
||||
}
|
||||
|
||||
/// Add headers to the headers collection.
|
||||
pub fn add<H>(&mut self, header: H)
|
||||
where
|
||||
H: AsHeaders,
|
||||
{
|
||||
for (key, value) in header.as_headers() {
|
||||
self.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all the header name/value pairs.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&HeaderName, &HeaderValue)> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Headers {
|
||||
type Item = (HeaderName, HeaderValue);
|
||||
|
||||
type IntoIter = std::collections::hash_map::IntoIter<HeaderName, HeaderValue>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::collections::HashMap<HeaderName, HeaderValue>> for Headers {
|
||||
fn from(c: std::collections::HashMap<HeaderName, HeaderValue>) -> Self {
|
||||
Self(c)
|
||||
}
|
||||
}
|
||||
|
||||
/// A header name.
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct HeaderName(Cow<'static, str>);
|
||||
|
||||
impl HeaderName {
|
||||
/// Create a header name from a static `str`.
|
||||
pub const fn from_static(s: &'static str) -> Self {
|
||||
ensure_no_uppercase(s);
|
||||
Self(Cow::Borrowed(s))
|
||||
}
|
||||
|
||||
fn from_cow<C>(c: C) -> Self
|
||||
where
|
||||
C: Into<Cow<'static, str>>,
|
||||
{
|
||||
let c = c.into();
|
||||
assert!(
|
||||
c.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()),
|
||||
"header names must be lowercase: {c}"
|
||||
);
|
||||
Self(c)
|
||||
}
|
||||
|
||||
/// Get a header name as a `str`.
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures the supplied string does not contain any uppercase ascii characters
|
||||
const fn ensure_no_uppercase(s: &str) {
|
||||
let bytes = s.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
let byte = bytes[i];
|
||||
assert!(
|
||||
!(byte >= 65u8 && byte <= 90u8),
|
||||
"header names must not contain uppercase letters"
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for HeaderName {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Self::from_cow(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for HeaderName {
|
||||
fn from(s: String) -> Self {
|
||||
Self::from_cow(s.to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
/// A header value.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct HeaderValue(Cow<'static, str>);
|
||||
|
||||
impl HeaderValue {
|
||||
/// Create a header value from a static `str`.
|
||||
pub const fn from_static(s: &'static str) -> Self {
|
||||
Self(Cow::Borrowed(s))
|
||||
}
|
||||
|
||||
/// Create a header value from a [`Cow`].
|
||||
pub fn from_cow<C>(c: C) -> Self
|
||||
where
|
||||
C: Into<Cow<'static, str>>,
|
||||
{
|
||||
Self(c.into())
|
||||
}
|
||||
|
||||
/// Get a header value as a `str`.
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for HeaderValue {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Self::from_cow(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for HeaderValue {
|
||||
fn from(s: String) -> Self {
|
||||
Self::from_cow(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for HeaderValue {
|
||||
fn from(s: &String) -> Self {
|
||||
s.clone().into()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
pub mod headers;
|
||||
mod response;
|
||||
|
||||
pub use response::*;
|
||||
|
||||
pub use http_types::StatusCode;
|
|
@ -1,18 +1,20 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::{
|
||||
error::{ErrorKind, ResultExt},
|
||||
headers::Headers,
|
||||
http::{headers::Headers, StatusCode},
|
||||
json::from_json,
|
||||
StatusCode,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{fmt, marker::PhantomData, pin::Pin};
|
||||
use typespec::error::{ErrorKind, ResultExt};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) type PinnedStream = Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
|
||||
pub type PinnedStream = Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(crate) type PinnedStream = Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>;
|
||||
pub type PinnedStream = Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>;
|
||||
|
||||
/// An HTTP response.
|
||||
pub struct RawResponse {
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use bytes::Bytes;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
/// Serialize a type to JSON.
|
||||
pub fn to_json<T>(value: &T) -> crate::Result<Bytes>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Ok(Bytes::from(serde_json::to_vec(value)?))
|
||||
}
|
||||
|
||||
/// Reads the JSON from bytes.
|
||||
pub fn from_json<S, T>(body: S) -> crate::Result<T>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
serde_json::from_slice(body.as_ref()).map_err(Into::into)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod error;
|
||||
#[cfg(feature = "http")]
|
||||
pub mod http;
|
||||
#[cfg(feature = "json")]
|
||||
pub mod json;
|
||||
|
||||
pub use typespec::error::*;
|
Загрузка…
Ссылка в новой задаче