object_store 0.7.1

A generic object store interface for uniformly interacting with AWS S3, Google Cloud Storage, Azure Blob Storage and local files.
Documentation
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use std::time::Duration;

use humantime::{format_duration, parse_duration};
use reqwest::header::HeaderValue;

use crate::{Error, Result};

/// Provides deferred parsing of a value
///
/// This allows builders to defer fallibility to build
#[derive(Debug, Clone)]
pub enum ConfigValue<T> {
    Parsed(T),
    Deferred(String),
}

impl<T: Display> Display for ConfigValue<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Parsed(v) => write!(f, "{v}"),
            Self::Deferred(v) => write!(f, "{v}"),
        }
    }
}

impl<T> From<T> for ConfigValue<T> {
    fn from(value: T) -> Self {
        Self::Parsed(value)
    }
}

impl<T: Parse + Clone> ConfigValue<T> {
    pub fn parse(&mut self, v: impl Into<String>) {
        *self = Self::Deferred(v.into())
    }

    pub fn get(&self) -> Result<T> {
        match self {
            Self::Parsed(v) => Ok(v.clone()),
            Self::Deferred(v) => T::parse(v),
        }
    }
}

impl<T: Default> Default for ConfigValue<T> {
    fn default() -> Self {
        Self::Parsed(T::default())
    }
}

/// A value that can be stored in [`ConfigValue`]
pub trait Parse: Sized {
    fn parse(v: &str) -> Result<Self>;
}

impl Parse for bool {
    fn parse(v: &str) -> Result<Self> {
        let lower = v.to_ascii_lowercase();
        match lower.as_str() {
            "1" | "true" | "on" | "yes" | "y" => Ok(true),
            "0" | "false" | "off" | "no" | "n" => Ok(false),
            _ => Err(Error::Generic {
                store: "Config",
                source: format!("failed to parse \"{v}\" as boolean").into(),
            }),
        }
    }
}

impl Parse for Duration {
    fn parse(v: &str) -> Result<Self> {
        parse_duration(v).map_err(|_| Error::Generic {
            store: "Config",
            source: format!("failed to parse \"{v}\" as Duration").into(),
        })
    }
}

impl Parse for usize {
    fn parse(v: &str) -> Result<Self> {
        Self::from_str(v).map_err(|_| Error::Generic {
            store: "Config",
            source: format!("failed to parse \"{v}\" as usize").into(),
        })
    }
}

impl Parse for HeaderValue {
    fn parse(v: &str) -> Result<Self> {
        Self::from_str(v).map_err(|_| Error::Generic {
            store: "Config",
            source: format!("failed to parse \"{v}\" as HeaderValue").into(),
        })
    }
}

pub(crate) fn fmt_duration(duration: &ConfigValue<Duration>) -> String {
    match duration {
        ConfigValue::Parsed(v) => format_duration(*v).to_string(),
        ConfigValue::Deferred(v) => v.clone(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn test_parse_duration() {
        let duration = Duration::from_secs(60);
        assert_eq!(Duration::parse("60 seconds").unwrap(), duration);
        assert_eq!(Duration::parse("60 s").unwrap(), duration);
        assert_eq!(Duration::parse("60s").unwrap(), duration)
    }
}