#![warn(missing_docs)]
extern crate base64;
extern crate curl;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate rand;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate sha2;
extern crate url;
use std::convert::Into;
use std::fmt::Error as FormatterError;
use std::fmt::{Debug, Display, Formatter};
use std::io::Read;
use std::marker::PhantomData;
use std::ops::Deref;
use std::time::Duration;
use curl::easy::Easy;
use rand::{thread_rng, Rng};
use serde::de::DeserializeOwned;
use serde::Serialize;
use sha2::{Digest, Sha256};
use url::Url;
use prelude::*;
const CONTENT_TYPE_JSON: &str = "application/json";
#[derive(Clone, Debug)]
pub enum AuthType {
RequestBody,
BasicAuth,
}
pub mod prelude {
use std::fmt::Debug;
use std::ops::Deref;
pub trait NewType<T>: Clone + Debug + Deref + PartialEq {
fn new(val: T) -> Self;
}
pub trait SecretNewType<T>: Debug {
fn new(val: T) -> Self
where
Self: Sized;
fn secret(&self) -> &T;
}
}
macro_rules! new_type {
(
$(#[$attr:meta])*
$name:ident(
$(#[$type_attr:meta])*
$type:ty
)
) => {
new_type![
@new_type $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {}
];
};
(
$(#[$attr:meta])*
$name:ident(
$(#[$type_attr:meta])*
$type:ty
)
impl {
$($item:tt)*
}
) => {
new_type![
@new_type $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {
$($item)*
}
];
};
(
@new_type $(#[$attr:meta])*,
$name:ident(
$(#[$type_attr:meta])*
$type:ty
),
$new_doc:expr,
impl {
$($item:tt)*
}
) => {
$(#[$attr])*
#[derive(Clone, Debug, PartialEq)]
pub struct $name(
$(#[$type_attr])*
$type
);
impl $name {
$($item)*
}
impl NewType<$type> for $name {
#[doc = $new_doc]
fn new(s: $type) -> Self {
$name(s)
}
}
impl Deref for $name {
type Target = $type;
fn deref(&self) -> &$type {
&self.0
}
}
impl Into<$type> for $name {
fn into(self) -> $type {
self.0
}
}
}
}
macro_rules! new_secret_type {
(
$(#[$attr:meta])*
$name:ident($type:ty)
) => {
new_secret_type![
$(#[$attr])*
$name($type)
impl {}
];
};
(
$(#[$attr:meta])*
$name:ident($type:ty)
impl {
$($item:tt)*
}
) => {
new_secret_type![
$(#[$attr])*,
$name($type),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
concat!("Get the secret contained within this `", stringify!($name), "`."),
impl {
$($item)*
}
];
};
(
$(#[$attr:meta])*,
$name:ident($type:ty),
$new_doc:expr,
$secret_doc:expr,
impl {
$($item:tt)*
}
) => {
$(
#[$attr]
)*
#[derive(Clone, PartialEq)]
pub struct $name($type);
impl $name {
$($item)*
}
impl SecretNewType<$type> for $name {
#[doc = $new_doc]
fn new(s: $type) -> Self {
$name(s)
}
#[doc = $secret_doc]
fn secret(&self) -> &$type { &self.0 }
}
impl Debug for $name {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
write!(f, concat!(stringify!($name), "([redacted])"))
}
}
};
}
new_type![#[derive(Deserialize, Serialize)]
ClientId(String)];
new_type![#[derive(Deserialize, Serialize)]
AuthUrl(
#[serde(
deserialize_with = "helpers::deserialize_url",
serialize_with = "helpers::serialize_url"
)]
Url
)];
new_type![#[derive(Deserialize, Serialize)]
TokenUrl(
#[serde(
deserialize_with = "helpers::deserialize_url",
serialize_with = "helpers::serialize_url"
)]
Url
)];
new_type![#[derive(Deserialize, Serialize)]
RedirectUrl(
#[serde(
deserialize_with = "helpers::deserialize_url",
serialize_with = "helpers::serialize_url"
)]
Url
)];
new_type![#[derive(Deserialize, Serialize)]
ResponseType(String)];
new_type![ResourceOwnerUsername(String)];
new_type![#[derive(Deserialize, Serialize)]
Scope(String)];
impl AsRef<str> for Scope {
fn as_ref(&self) -> &str {
self
}
}
new_type![#[derive(Deserialize, Serialize)]
PkceCodeChallengeS256(String)];
new_type![#[derive(Deserialize, Serialize)]
PkceCodeChallengeMethod(String)];
new_secret_type![#[derive(Deserialize, Serialize)]
ClientSecret(String)];
new_secret_type![
#[must_use]
#[derive(Deserialize, Serialize)]
CsrfToken(String)
impl {
pub fn new_random() -> Self {
CsrfToken::new_random_len(16)
}
pub fn new_random_len(num_bytes: u32) -> Self {
let random_bytes: Vec<u8> = (0..num_bytes).map(|_| thread_rng().gen::<u8>()).collect();
CsrfToken::new(base64::encode_config(&random_bytes, base64::URL_SAFE_NO_PAD))
}
}
];
new_secret_type![
#[derive(Deserialize, Serialize)]
PkceCodeVerifierS256(String)
impl {
pub fn new_random() -> Self {
PkceCodeVerifierS256::new_random_len(32)
}
pub fn new_random_len(num_bytes: u32) -> Self {
assert!(num_bytes >= 32 && num_bytes <= 96);
let random_bytes: Vec<u8> = (0..num_bytes).map(|_| thread_rng().gen::<u8>()).collect();
let code = base64::encode_config(&random_bytes, base64::URL_SAFE_NO_PAD);
assert!(code.len() >=43 && code.len() <= 128);
PkceCodeVerifierS256::new(code)
}
pub fn code_challenge(&self) -> PkceCodeChallengeS256 {
let digest = Sha256::digest(self.secret().as_bytes());
PkceCodeChallengeS256::new(base64::encode_config(&digest, base64::URL_SAFE_NO_PAD))
}
pub fn code_challenge_method() -> PkceCodeChallengeMethod {
PkceCodeChallengeMethod::new("S256".to_string())
}
pub fn authorize_url_params(&self) -> Vec<(&'static str, String)> {
vec![
(
"code_challenge_method",
PkceCodeVerifierS256::code_challenge_method().into(),
),
("code_challenge", self.code_challenge().into()),
]
}
}
];
new_secret_type![#[derive(Deserialize, Serialize)]
AuthorizationCode(String)];
new_secret_type![#[derive(Deserialize, Serialize)]
RefreshToken(String)];
new_secret_type![#[derive(Deserialize, Serialize)]
AccessToken(String)];
new_secret_type![ResourceOwnerPassword(String)];
#[derive(Clone, Debug)]
pub struct Client<TE, TR, TT>
where
TE: ErrorResponseType,
TR: TokenResponse<TT>,
TT: TokenType,
{
client_id: ClientId,
client_secret: Option<ClientSecret>,
auth_url: AuthUrl,
auth_type: AuthType,
token_url: Option<TokenUrl>,
scopes: Vec<Scope>,
redirect_url: Option<RedirectUrl>,
phantom_te: PhantomData<TE>,
phantom_tr: PhantomData<TR>,
phantom_tt: PhantomData<TT>,
}
impl<TE, TR, TT> Client<TE, TR, TT>
where
TE: ErrorResponseType,
TR: TokenResponse<TT>,
TT: TokenType,
{
pub fn new(
client_id: ClientId,
client_secret: Option<ClientSecret>,
auth_url: AuthUrl,
token_url: Option<TokenUrl>,
) -> Self {
Client {
client_id,
client_secret,
auth_url,
auth_type: AuthType::BasicAuth,
token_url,
scopes: Vec::new(),
redirect_url: None,
phantom_te: PhantomData,
phantom_tr: PhantomData,
phantom_tt: PhantomData,
}
}
pub fn add_scope(mut self, scope: Scope) -> Self {
self.scopes.push(scope);
self
}
pub fn set_auth_type(mut self, auth_type: AuthType) -> Self {
self.auth_type = auth_type;
self
}
pub fn set_redirect_url(mut self, redirect_url: RedirectUrl) -> Self {
self.redirect_url = Some(redirect_url);
self
}
pub fn authorize_url<F>(&self, state_fn: F) -> (Url, CsrfToken)
where
F: FnOnce() -> CsrfToken,
{
let state = state_fn();
(
self.authorize_url_impl::<&str>("code", Some(&state), None),
state,
)
}
pub fn authorize_url_implicit<F>(&self, state_fn: F) -> (Url, CsrfToken)
where
F: FnOnce() -> CsrfToken,
{
let state = state_fn();
(
self.authorize_url_impl::<&str>("token", Some(&state), None),
state,
)
}
pub fn authorize_url_extension<F, T>(
&self,
response_type: &ResponseType,
state_fn: F,
extra_params: &[(&str, T)],
) -> (Url, CsrfToken)
where
F: FnOnce() -> CsrfToken,
T: AsRef<str> + Clone,
{
let state = state_fn();
(
self.authorize_url_impl(response_type, Some(&state), Some(extra_params)),
state,
)
}
fn authorize_url_impl<T>(
&self,
response_type: &str,
state_opt: Option<&CsrfToken>,
extra_params_opt: Option<&[(&str, T)]>,
) -> Url
where
T: AsRef<str> + Clone,
{
let scopes = self
.scopes
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" ");
let mut pairs: Vec<(&str, &str)> = vec![
("response_type", response_type),
("client_id", &self.client_id),
];
if let Some(ref redirect_url) = self.redirect_url {
pairs.push(("redirect_uri", redirect_url.as_str()));
}
if !scopes.is_empty() {
pairs.push(("scope", &scopes));
}
if let Some(state) = state_opt {
pairs.push(("state", state.secret()));
}
let mut url: Url = (*self.auth_url).clone();
url.query_pairs_mut()
.extend_pairs(pairs.iter().map(|&(k, v)| (k, &v[..])));
if let Some(extra_params) = extra_params_opt {
url.query_pairs_mut()
.extend_pairs(extra_params.iter().cloned());
}
url
}
pub fn exchange_code(&self, code: AuthorizationCode) -> Result<TR, RequestTokenError<TE>> {
self.exchange_code_extension::<&str>(code, &[])
}
pub fn exchange_code_extension<T>(
&self,
code: AuthorizationCode,
extra_params: &[(&str, T)],
) -> Result<TR, RequestTokenError<TE>>
where
T: AsRef<str> + Clone,
{
let code_owned = code;
let mut params: Vec<(&str, &str)> = vec![
("grant_type", "authorization_code"),
("code", code_owned.secret()),
];
params.extend_from_slice(
&extra_params
.iter()
.map(|&(k, ref v)| (k, v.as_ref()))
.collect::<Vec<(&str, &str)>>(),
);
self.request_token(params)
}
pub fn exchange_password(
&self,
username: &ResourceOwnerUsername,
password: &ResourceOwnerPassword,
) -> Result<TR, RequestTokenError<TE>> {
let scopes_opt = if !self.scopes.is_empty() {
Some(
self.scopes
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" "),
)
} else {
None
};
let mut params = vec![
("grant_type", "password"),
("username", username),
("password", password.secret()),
];
if let Some(ref scopes) = scopes_opt {
params.push(("scope", scopes));
}
self.request_token(params)
}
pub fn exchange_client_credentials(&self) -> Result<TR, RequestTokenError<TE>> {
let scopes_opt = if !self.scopes.is_empty() {
Some(
self.scopes
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" "),
)
} else {
None
};
let mut params: Vec<(&str, &str)> = vec![("grant_type", "client_credentials")];
if let Some(ref scopes) = scopes_opt {
params.push(("scope", scopes));
}
self.request_token(params)
}
pub fn exchange_refresh_token(
&self,
refresh_token: &RefreshToken,
) -> Result<TR, RequestTokenError<TE>> {
self.exchange_refresh_token_extension::<&str>(refresh_token, &[])
}
pub fn exchange_refresh_token_extension<T>(
&self,
refresh_token: &RefreshToken,
extra_params: &[(&str, T)],
) -> Result<TR, RequestTokenError<TE>>
where
T: AsRef<str> + Clone,
{
let mut params: Vec<(&str, &str)> = vec![
("grant_type", "refresh_token"),
("refresh_token", refresh_token.secret()),
];
params.extend_from_slice(
&extra_params
.iter()
.map(|&(k, ref v)| (k, v.as_ref()))
.collect::<Vec<(&str, &str)>>(),
);
self.request_token(params)
}
fn post_request_token<'a, 'b: 'a>(
&'b self,
token_url: &TokenUrl,
mut params: Vec<(&'b str, &'a str)>,
) -> Result<RequestTokenResponse, curl::Error> {
let mut easy = Easy::new();
match self.auth_type {
AuthType::RequestBody => {
params.push(("client_id", &self.client_id));
if let Some(ref client_secret) = self.client_secret {
params.push(("client_secret", client_secret.secret()));
}
}
AuthType::BasicAuth => {
let encoded_id = easy.url_encode(&self.client_id.as_bytes());
easy.username(&encoded_id)?;
if let Some(ref client_secret) = self.client_secret {
let encoded_secret = easy.url_encode(client_secret.secret().as_bytes());
easy.password(&encoded_secret)?;
}
}
}
if let Some(ref redirect_url) = self.redirect_url {
params.push(("redirect_uri", redirect_url.as_str()));
}
let form = url::form_urlencoded::Serializer::new(String::new())
.extend_pairs(params)
.finish()
.into_bytes();
let mut form_slice = &form[..];
easy.url(&token_url.to_string()[..])?;
let mut headers = curl::easy::List::new();
let accept_header = format!("Accept: {}", CONTENT_TYPE_JSON);
headers.append(&accept_header)?;
easy.http_headers(headers)?;
easy.post(true)?;
easy.post_field_size(form.len() as u64)?;
let mut data = Vec::new();
{
let mut transfer = easy.transfer();
transfer.read_function(|buf| Ok(form_slice.read(buf).unwrap_or(0)))?;
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
})?;
transfer.perform()?;
}
let http_status = easy.response_code()?;
let content_type = easy.content_type()?;
Ok(RequestTokenResponse {
http_status,
content_type: content_type.map(|s| s.to_string()),
response_body: data,
})
}
fn request_token(&self, params: Vec<(&str, &str)>) -> Result<TR, RequestTokenError<TE>> {
let token_url = self.token_url.as_ref().ok_or_else(||
RequestTokenError::Other("token_url must not be `None`".to_string()))?;
let token_response = self
.post_request_token(token_url, params)
.map_err(RequestTokenError::Request)?;
if token_response.http_status != 200 {
let reason = token_response.response_body.as_slice();
if reason.is_empty() {
return Err(RequestTokenError::Other(
"Server returned empty error response".to_string(),
));
} else {
let error = match serde_json::from_slice::<ErrorResponse<TE>>(reason) {
Ok(error) => RequestTokenError::ServerResponse(error),
Err(error) => RequestTokenError::Parse(error, reason.to_vec()),
};
return Err(error);
}
}
token_response
.content_type
.map_or(Ok(()), |content_type|
if !content_type.to_lowercase().starts_with(CONTENT_TYPE_JSON) {
Err(
RequestTokenError::Other(
format!(
"Unexpected response Content-Type: `{}`, should be `{}`",
content_type,
CONTENT_TYPE_JSON
)
)
)
} else {
Ok(())
}
)?;
if token_response.response_body.is_empty() {
Err(RequestTokenError::Other(
"Server returned empty response body".to_string(),
))
} else {
let response_body = token_response.response_body.as_slice();
serde_json::from_slice(response_body)
.map_err(|e| RequestTokenError::Parse(e, response_body.to_vec()))
}
}
}
struct RequestTokenResponse {
http_status: u32,
content_type: Option<String>,
response_body: Vec<u8>,
}
pub trait TokenType: Clone + DeserializeOwned + Debug + PartialEq + Serialize {}
pub trait ExtraTokenFields: Clone + DeserializeOwned + Debug + PartialEq + Serialize {}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct EmptyExtraTokenFields {}
impl ExtraTokenFields for EmptyExtraTokenFields {}
pub trait TokenResponse<TT>: Clone + Debug + DeserializeOwned + PartialEq + Serialize
where
TT: TokenType,
{
fn access_token(&self) -> &AccessToken;
fn token_type(&self) -> &TT;
fn expires_in(&self) -> Option<Duration>;
fn refresh_token(&self) -> Option<&RefreshToken>;
fn scopes(&self) -> Option<&Vec<Scope>>;
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct StandardTokenResponse<EF: ExtraTokenFields, TT: TokenType> {
access_token: AccessToken,
#[serde(bound = "TT: TokenType")]
#[serde(deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive")]
token_type: TT,
#[serde(skip_serializing_if = "Option::is_none")]
expires_in: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
refresh_token: Option<RefreshToken>,
#[serde(rename = "scope")]
#[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
#[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
scopes: Option<Vec<Scope>>,
#[serde(bound = "EF: ExtraTokenFields")]
#[serde(flatten)]
extra_fields: EF,
}
impl<EF, TT> StandardTokenResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType,
{
pub fn new(access_token: AccessToken, token_type: TT, extra_fields: EF) -> Self {
Self {
access_token,
token_type,
expires_in: None,
refresh_token: None,
scopes: None,
extra_fields,
}
}
pub fn set_access_token(&mut self, access_token: AccessToken) {
self.access_token = access_token;
}
pub fn set_token_type(&mut self, token_type: TT) {
self.token_type = token_type;
}
pub fn set_expires_in(&mut self, expires_in: Option<u64>) {
self.expires_in = expires_in;
}
pub fn set_refresh_token(&mut self, refresh_token: Option<RefreshToken>) {
self.refresh_token = refresh_token;
}
pub fn set_scopes(&mut self, scopes: Option<Vec<Scope>>) {
self.scopes = scopes;
}
pub fn extra_fields(&self) -> &EF {
&self.extra_fields
}
pub fn set_extra_fields(&mut self, extra_fields: EF) {
self.extra_fields = extra_fields;
}
}
impl<EF, TT> TokenResponse<TT> for StandardTokenResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType,
{
fn access_token(&self) -> &AccessToken {
&self.access_token
}
fn token_type(&self) -> &TT {
&self.token_type
}
fn expires_in(&self) -> Option<Duration> {
self.expires_in.map(Duration::from_secs)
}
fn refresh_token(&self) -> Option<&RefreshToken> {
self.refresh_token.as_ref()
}
fn scopes(&self) -> Option<&Vec<Scope>> {
self.scopes.as_ref()
}
}
pub trait ErrorResponseType:
Clone + Debug + DeserializeOwned + Display + PartialEq + Send + Serialize + Sync
{
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ErrorResponse<T: ErrorResponseType> {
#[serde(bound = "T: ErrorResponseType")]
error: T,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
error_description: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
error_uri: Option<String>,
}
impl<T: ErrorResponseType> ErrorResponse<T> {
pub fn new(error: T, error_description: Option<String>, error_uri: Option<String>) -> Self {
Self {
error,
error_description,
error_uri,
}
}
pub fn error(&self) -> &T {
&self.error
}
pub fn error_description(&self) -> Option<&String> {
self.error_description.as_ref()
}
pub fn error_uri(&self) -> Option<&String> {
self.error_uri.as_ref()
}
}
impl<TE: ErrorResponseType> Display for ErrorResponse<TE> {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
let mut formatted = self.error().to_string();
if let Some(error_description) = self.error_description() {
formatted.push_str(": ");
formatted.push_str(error_description);
}
if let Some(error_uri) = self.error_uri() {
formatted.push_str(" / See ");
formatted.push_str(error_uri);
}
write!(f, "{}", formatted)
}
}
#[derive(Debug, Fail)]
pub enum RequestTokenError<T: ErrorResponseType + Send + Sync + 'static> {
#[fail(display = "Server returned error response `{}`", _0)]
ServerResponse(ErrorResponse<T>),
#[fail(display = "Request failed")]
Request(#[cause] curl::Error),
#[fail(display = "Failed to parse server response")]
Parse(#[cause] serde_json::error::Error, Vec<u8>),
#[fail(display = "Other error: {}", _0)]
Other(String),
}
pub mod basic {
extern crate serde_json;
use std::fmt::Error as FormatterError;
use std::fmt::{Debug, Display, Formatter};
use super::helpers;
use super::{
Client, EmptyExtraTokenFields, ErrorResponse, ErrorResponseType, RequestTokenError,
StandardTokenResponse, TokenType,
};
pub type BasicClient = Client<BasicErrorResponseType, BasicTokenResponse, BasicTokenType>;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum BasicTokenType {
Bearer,
Mac,
}
impl TokenType for BasicTokenType {}
pub type BasicTokenResponse = StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>;
#[derive(Clone, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BasicErrorResponseType {
InvalidRequest,
InvalidClient,
InvalidGrant,
UnauthorizedClient,
UnsupportedGrantType,
InvalidScope,
}
impl ErrorResponseType for BasicErrorResponseType {}
impl Debug for BasicErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
Display::fmt(self, f)
}
}
impl Display for BasicErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
write!(f, "{}", helpers::variant_name(&self))
}
}
pub type BasicErrorResponse = ErrorResponse<BasicErrorResponseType>;
pub type BasicRequestTokenError = RequestTokenError<BasicErrorResponseType>;
}
pub mod insecure {
use url::Url;
use super::{Client, ErrorResponseType, TokenResponse, TokenType};
pub fn authorize_url<TE, TR, TT>(client: &Client<TE, TR, TT>) -> Url
where
TE: ErrorResponseType,
TR: TokenResponse<TT>,
TT: TokenType,
{
client.authorize_url_impl::<&str>("code", None, None)
}
pub fn authorize_url_implicit<TE, TR, TT>(client: &Client<TE, TR, TT>) -> Url
where
TE: ErrorResponseType,
TR: TokenResponse<TT>,
TT: TokenType,
{
client.authorize_url_impl::<&str>("token", None, None)
}
}
pub mod helpers {
use std;
use serde::ser;
use serde::ser::{Impossible, SerializeStructVariant, SerializeTupleVariant};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use url::Url;
pub fn deserialize_untagged_enum_case_insensitive<'de, T, D>(
deserializer: D,
) -> Result<T, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
use serde::de::Error;
use serde_json::Value;
T::deserialize(Value::String(
String::deserialize(deserializer)?.to_lowercase(),
))
.map_err(Error::custom)
}
pub fn deserialize_space_delimited_vec<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Default + Deserialize<'de>,
D: Deserializer<'de>,
{
use serde::de::Error;
use serde_json::Value;
if let Some(space_delimited) = Option::<String>::deserialize(deserializer)? {
let entries = space_delimited
.split(' ')
.map(|s| Value::String(s.to_string()))
.collect();
T::deserialize(Value::Array(entries)).map_err(Error::custom)
} else {
Ok(T::default())
}
}
pub fn serialize_space_delimited_vec<T, S>(
vec_opt: &Option<Vec<T>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
T: AsRef<str>,
S: Serializer,
{
if let Some(ref vec) = *vec_opt {
let space_delimited = vec.iter().map(|s| s.as_ref()).collect::<Vec<_>>().join(" ");
serializer.serialize_str(&space_delimited)
} else {
serializer.serialize_none()
}
}
pub fn deserialize_url<'de, D>(deserializer: D) -> Result<Url, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let url_str = String::deserialize(deserializer)?;
Url::parse(url_str.as_ref()).map_err(Error::custom)
}
pub fn serialize_url<S>(url: &Url, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(url.as_str())
}
pub fn variant_name<T: Serialize>(t: &T) -> &'static str {
#[derive(Debug)]
struct NotEnum;
type Result<T> = std::result::Result<T, NotEnum>;
impl std::error::Error for NotEnum {
fn description(&self) -> &str {
"not struct"
}
}
impl std::fmt::Display for NotEnum {
fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
unimplemented!()
}
}
impl ser::Error for NotEnum {
fn custom<T: std::fmt::Display>(_msg: T) -> Self {
NotEnum
}
}
struct VariantName;
impl Serializer for VariantName {
type Ok = &'static str;
type Error = NotEnum;
type SerializeSeq = Impossible<Self::Ok, Self::Error>;
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
type SerializeTupleVariant = Enum;
type SerializeMap = Impossible<Self::Ok, Self::Error>;
type SerializeStruct = Impossible<Self::Ok, Self::Error>;
type SerializeStructVariant = Enum;
fn serialize_bool(self, _v: bool) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i8(self, _v: i8) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i16(self, _v: i16) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i32(self, _v: i32) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i64(self, _v: i64) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u8(self, _v: u8) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u16(self, _v: u16) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u32(self, _v: u32) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u64(self, _v: u64) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_f32(self, _v: f32) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_f64(self, _v: f64) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_char(self, _v: char) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_str(self, _v: &str) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_none(self) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_unit(self) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok> {
Ok(variant)
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(
self,
_name: &'static str,
_value: &T,
) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_value: &T,
) -> Result<Self::Ok> {
Ok(variant)
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
Err(NotEnum)
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
Err(NotEnum)
}
fn serialize_tuple_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleStruct> {
Err(NotEnum)
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
Ok(Enum(variant))
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
Err(NotEnum)
}
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct> {
Err(NotEnum)
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
Ok(Enum(variant))
}
}
struct Enum(&'static str);
impl SerializeStructVariant for Enum {
type Ok = &'static str;
type Error = NotEnum;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
_key: &'static str,
_value: &T,
) -> Result<()> {
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.0)
}
}
impl SerializeTupleVariant for Enum {
type Ok = &'static str;
type Error = NotEnum;
fn serialize_field<T: ?Sized + Serialize>(&mut self, _value: &T) -> Result<()> {
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.0)
}
}
t.serialize(VariantName).unwrap()
}
}