hevy 0.1.0

An async Rust client library for the Hevy public API (https://api.hevyapp.com/docs/)
Documentation
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;

/// The type of a logged or planned set.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SetType {
    Warmup,
    Normal,
    Failure,
    Dropset,
}

/// The type of a custom exercise template.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CustomExerciseType {
    WeightReps,
    RepsOnly,
    BodyweightReps,
    BodyweightAssistedReps,
    Duration,
    WeightDuration,
    DistanceDuration,
    ShortDistanceWeight,
}

/// A primary or secondary muscle group targeted by an exercise.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MuscleGroup {
    Abdominals,
    Shoulders,
    Biceps,
    Triceps,
    Forearms,
    Quadriceps,
    Hamstrings,
    Calves,
    Glutes,
    Abductors,
    Adductors,
    Lats,
    UpperBack,
    Traps,
    LowerBack,
    Chest,
    Cardio,
    Neck,
    FullBody,
    Other,
}

/// The equipment category used by an exercise.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EquipmentCategory {
    None,
    Barbell,
    Dumbbell,
    Kettlebell,
    Machine,
    Plate,
    ResistanceBand,
    Suspension,
    Other,
}

/// A target rep range (e.g. "8-12 reps") used by routine sets.
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct RepRange {
    /// The lower bound of the rep range.
    #[serde(default)]
    pub start: Option<f64>,
    /// The upper bound of the rep range.
    #[serde(default)]
    pub end: Option<f64>,
}

impl RepRange {
    /// Creates a new rep range spanning `start..=end`.
    pub fn new(start: f64, end: f64) -> Self {
        Self {
            start: Some(start),
            end: Some(end),
        }
    }
}

/// Deserializes a field that the Hevy API may represent as either a JSON number
/// or a numeric string, tolerating documented schema inconsistencies.
pub(crate) fn flexible_f64<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
where
    D: Deserializer<'de>,
{
    let value: Option<Value> = Option::deserialize(deserializer)?;
    match value {
        None | Some(Value::Null) => Ok(None),
        Some(Value::Number(n)) => Ok(n.as_f64()),
        Some(Value::String(s)) if s.is_empty() => Ok(None),
        Some(Value::String(s)) => s
            .parse::<f64>()
            .map(Some)
            .map_err(|e| serde::de::Error::custom(format!("invalid number `{s}`: {e}"))),
        Some(other) => Err(serde::de::Error::custom(format!(
            "expected a number or numeric string, got {other}"
        ))),
    }
}