1use std::str::FromStr;
4
5use clap::ValueEnum;
6use serde::{Deserialize, Serialize};
7
8use crate::error::TemperatureParseError;
9
10#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)]
12pub enum Temperature {
13 Celsius,
15 Fahrenheit,
17 Kelvin,
19}
20
21impl Temperature {
22 const KELVIN_BASELINE: f64 = 273.15;
23 const FAHRENHEIT_COEF: f64 = 5.0 / 9.0;
24 const REVERSE_FAHRENHEIT_COEF: f64 = 9.0 / 5.0;
25 const FAHRENHEIT_BASELINE: f64 = 32.0;
26
27 #[inline]
40 pub fn convert(&self, target_temperature: Temperature, value: f64) -> f64 {
41 match self {
42 Temperature::Celsius => match target_temperature {
43 Temperature::Celsius => value,
44 Temperature::Kelvin => value + Temperature::KELVIN_BASELINE,
45 Temperature::Fahrenheit => {
46 value * Temperature::REVERSE_FAHRENHEIT_COEF + Temperature::FAHRENHEIT_BASELINE
47 }
48 },
49 Temperature::Kelvin => match target_temperature {
50 Temperature::Celsius => value - Temperature::KELVIN_BASELINE,
51 Temperature::Kelvin => value,
52 Temperature::Fahrenheit => {
53 (value - Temperature::KELVIN_BASELINE) * Temperature::REVERSE_FAHRENHEIT_COEF
54 + Temperature::FAHRENHEIT_BASELINE
55 }
56 },
57 Temperature::Fahrenheit => match target_temperature {
58 Temperature::Celsius => {
59 (value - Temperature::FAHRENHEIT_BASELINE) * Temperature::FAHRENHEIT_COEF
60 }
61 Temperature::Kelvin => {
62 (value - Temperature::FAHRENHEIT_BASELINE) * Temperature::FAHRENHEIT_COEF
63 + Temperature::KELVIN_BASELINE
64 }
65 Temperature::Fahrenheit => value,
66 },
67 }
68 }
69}
70
71impl FromStr for Temperature {
72 type Err = crate::error::TemperatureParseError;
73
74 #[inline]
75 fn from_str(s: &str) -> Result<Self, Self::Err> {
76 match s {
77 "Kelvin" | "kelvin" | "k" => Ok(Temperature::Kelvin),
78 "Celsius" | "celsius" | "c" => Ok(Temperature::Celsius),
79 "Fahrenheit" | "fahrenheit" | "f" => Ok(Temperature::Fahrenheit),
80 _ => Err(TemperatureParseError::InvalidTemperature(s.to_string())),
81 }
82 }
83}
84
85impl std::fmt::Display for Temperature {
86 #[inline]
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 match self {
89 Temperature::Celsius => write!(f, "Celsius"),
90 Temperature::Fahrenheit => write!(f, "Fahrenheit"),
91 Temperature::Kelvin => write!(f, "Kelvin"),
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use std::str::FromStr;
99
100 use super::{
101 super::error::TemperatureParseError,
102 Temperature::{self, *},
103 };
104
105 use assert_float_eq::*;
106 use pretty_assertions::assert_eq;
107 use rstest::rstest;
108
109 #[rstest]
110 #[case(Celsius, Kelvin, 0.0, 273.15)]
111 #[case(Celsius, Kelvin, 27.0, 300.15)]
112 #[case(Celsius, Kelvin, -200.0, 73.15)]
113 #[case(Celsius, Kelvin, 9031.99, 9305.14)]
114 #[case(Kelvin, Celsius, 273.15, 0.0)]
115 #[case(Kelvin, Celsius, 300.15, 27.0)]
116 #[case(Kelvin, Celsius, 9031.99, 8758.84)]
117 #[case(Kelvin, Celsius, -9031.99, -9305.14)]
118 #[case(Fahrenheit, Celsius, 32.0, 0.0)]
119 #[case(Fahrenheit, Celsius, 50.0, 10.0)]
120 #[case(Fahrenheit, Celsius, 14.0, -10.0)]
121 #[case(Fahrenheit, Celsius, -4.0, -20.0)]
122 #[case(Celsius, Fahrenheit, 5.0, 41.0)]
123 #[case(Celsius, Fahrenheit, -5.0, 23.0)]
124 #[case(Celsius, Fahrenheit, 27.0, 80.6)]
125 #[case(Celsius, Fahrenheit, 0.0, 32.0)]
126 #[case(Fahrenheit, Kelvin, 32.0, 273.15)]
127 #[case(Fahrenheit, Kelvin, 50.0, 283.15)]
128 #[case(Fahrenheit, Kelvin, 14.0, 263.15)]
129 #[case(Fahrenheit, Kelvin, -4.0, 253.15)]
130 #[case(Kelvin, Fahrenheit, 0.0, -459.67)]
131 #[case(Kelvin, Fahrenheit, 100.0, -279.67)]
132 #[case(Kelvin, Fahrenheit, 155.5, -179.77)]
133 #[case(Kelvin, Fahrenheit, 2500.0, 4040.33)]
134 fn check_conversion(
135 #[case] original_temperature: Temperature,
136 #[case] target_temperature: Temperature,
137 #[case] original_value: f64,
138 #[case] expected_value: f64,
139 ) {
140 let result = original_temperature.convert(target_temperature, original_value);
141
142 assert_f64_near!(result, expected_value);
143 }
144
145 #[rstest]
146 #[case("Kelvin", Ok(Temperature::Kelvin))]
147 #[case("kelvin", Ok(Temperature::Kelvin))]
148 #[case("k", Ok(Temperature::Kelvin))]
149 #[case("Celsius", Ok(Temperature::Celsius))]
150 #[case("celsius", Ok(Temperature::Celsius))]
151 #[case("c", Ok(Temperature::Celsius))]
152 #[case("Fahrenheit", Ok(Temperature::Fahrenheit))]
153 #[case("fahrenheit", Ok(Temperature::Fahrenheit))]
154 #[case("f", Ok(Temperature::Fahrenheit))]
155 fn check_parsing(
156 #[case] input: &str,
157 #[case] expected: Result<Temperature, TemperatureParseError>,
158 ) {
159 assert_eq!(Temperature::from_str(input), expected);
160 }
161}