#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
fn normalized_key(value: &str) -> String {
value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
}
fn non_empty_text(value: impl AsRef<str>) -> Result<String, AtmosphericConditionError> {
let trimmed = value.as_ref().trim();
if trimmed.is_empty() {
Err(AtmosphericConditionError::Empty)
} else {
Ok(trimmed.to_string())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AtmosphericConditionError {
Empty,
}
impl fmt::Display for AtmosphericConditionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("atmospheric condition cannot be empty"),
}
}
}
impl Error for AtmosphericConditionError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum AtmosphereLayer {
Troposphere,
Stratosphere,
Mesosphere,
Thermosphere,
Exosphere,
Unknown,
Custom(String),
}
impl fmt::Display for AtmosphereLayer {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Troposphere => formatter.write_str("troposphere"),
Self::Stratosphere => formatter.write_str("stratosphere"),
Self::Mesosphere => formatter.write_str("mesosphere"),
Self::Thermosphere => formatter.write_str("thermosphere"),
Self::Exosphere => formatter.write_str("exosphere"),
Self::Unknown => formatter.write_str("unknown"),
Self::Custom(value) => formatter.write_str(value),
}
}
}
impl FromStr for AtmosphereLayer {
type Err = AtmosphereLayerParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(AtmosphereLayerParseError::Empty);
}
match normalized_key(trimmed).as_str() {
"troposphere" => Ok(Self::Troposphere),
"stratosphere" => Ok(Self::Stratosphere),
"mesosphere" => Ok(Self::Mesosphere),
"thermosphere" => Ok(Self::Thermosphere),
"exosphere" => Ok(Self::Exosphere),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AtmosphereLayerParseError {
Empty,
}
impl fmt::Display for AtmosphereLayerParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("atmosphere layer cannot be empty"),
}
}
}
impl Error for AtmosphereLayerParseError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum AirMassKind {
ContinentalPolar,
ContinentalTropical,
MaritimePolar,
MaritimeTropical,
Arctic,
Antarctic,
Unknown,
Custom(String),
}
impl fmt::Display for AirMassKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ContinentalPolar => formatter.write_str("continental-polar"),
Self::ContinentalTropical => formatter.write_str("continental-tropical"),
Self::MaritimePolar => formatter.write_str("maritime-polar"),
Self::MaritimeTropical => formatter.write_str("maritime-tropical"),
Self::Arctic => formatter.write_str("arctic"),
Self::Antarctic => formatter.write_str("antarctic"),
Self::Unknown => formatter.write_str("unknown"),
Self::Custom(value) => formatter.write_str(value),
}
}
}
impl FromStr for AirMassKind {
type Err = AirMassKindParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(AirMassKindParseError::Empty);
}
match normalized_key(trimmed).as_str() {
"continental-polar" => Ok(Self::ContinentalPolar),
"continental-tropical" => Ok(Self::ContinentalTropical),
"maritime-polar" => Ok(Self::MaritimePolar),
"maritime-tropical" => Ok(Self::MaritimeTropical),
"arctic" => Ok(Self::Arctic),
"antarctic" => Ok(Self::Antarctic),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AirMassKindParseError {
Empty,
}
impl fmt::Display for AirMassKindParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("air mass kind cannot be empty"),
}
}
}
impl Error for AirMassKindParseError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum VisibilityCondition {
Clear,
Haze,
Mist,
Fog,
Smoke,
Dust,
Unknown,
Custom(String),
}
impl fmt::Display for VisibilityCondition {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Clear => formatter.write_str("clear"),
Self::Haze => formatter.write_str("haze"),
Self::Mist => formatter.write_str("mist"),
Self::Fog => formatter.write_str("fog"),
Self::Smoke => formatter.write_str("smoke"),
Self::Dust => formatter.write_str("dust"),
Self::Unknown => formatter.write_str("unknown"),
Self::Custom(value) => formatter.write_str(value),
}
}
}
impl FromStr for VisibilityCondition {
type Err = VisibilityConditionParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(VisibilityConditionParseError::Empty);
}
match normalized_key(trimmed).as_str() {
"clear" => Ok(Self::Clear),
"haze" => Ok(Self::Haze),
"mist" => Ok(Self::Mist),
"fog" => Ok(Self::Fog),
"smoke" => Ok(Self::Smoke),
"dust" => Ok(Self::Dust),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VisibilityConditionParseError {
Empty,
}
impl fmt::Display for VisibilityConditionParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("visibility condition cannot be empty"),
}
}
}
impl Error for VisibilityConditionParseError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AtmosphericCondition(String);
impl AtmosphericCondition {
pub fn new(value: impl AsRef<str>) -> Result<Self, AtmosphericConditionError> {
non_empty_text(value).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for AtmosphericCondition {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for AtmosphericCondition {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for AtmosphericCondition {
type Err = AtmosphericConditionError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[cfg(test)]
mod tests {
use super::{
AirMassKind, AirMassKindParseError, AtmosphereLayer, AtmosphereLayerParseError,
AtmosphericCondition, AtmosphericConditionError, VisibilityCondition,
VisibilityConditionParseError,
};
use core::str::FromStr;
#[test]
fn atmosphere_layer_display_and_parse() {
assert_eq!(AtmosphereLayer::Stratosphere.to_string(), "stratosphere");
assert_eq!(
AtmosphereLayer::from_str("mesosphere").unwrap(),
AtmosphereLayer::Mesosphere
);
assert_eq!(
AtmosphereLayer::from_str(" "),
Err(AtmosphereLayerParseError::Empty)
);
}
#[test]
fn air_mass_display_and_parse() {
assert_eq!(AirMassKind::MaritimePolar.to_string(), "maritime-polar");
assert_eq!(
AirMassKind::from_str("continental tropical").unwrap(),
AirMassKind::ContinentalTropical
);
assert_eq!(
AirMassKind::from_str(" "),
Err(AirMassKindParseError::Empty)
);
}
#[test]
fn visibility_condition_display_and_parse() {
assert_eq!(VisibilityCondition::Fog.to_string(), "fog");
assert_eq!(
VisibilityCondition::from_str("smoke").unwrap(),
VisibilityCondition::Smoke
);
assert_eq!(
VisibilityCondition::from_str(" "),
Err(VisibilityConditionParseError::Empty)
);
}
#[test]
fn custom_atmosphere_layer() {
assert_eq!(
AtmosphereLayer::from_str("planetary boundary layer").unwrap(),
AtmosphereLayer::Custom(String::from("planetary boundary layer"))
);
}
#[test]
fn atmospheric_condition_requires_text() {
assert_eq!(
AtmosphericCondition::new(" "),
Err(AtmosphericConditionError::Empty)
);
assert_eq!(
AtmosphericCondition::new("unstable low levels")
.unwrap()
.as_str(),
"unstable low levels"
);
}
}