1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
use std::str::FromStr;
use strum::IntoEnumIterator;
use strum_macros::{AsRefStr, Display, EnumIter, EnumString, IntoStaticStr};

pub trait Filters {
    fn is_all(&self) -> bool;
}

/// General authorizations to access the iChen System via Open Protocol.
///
/// For details, see [this document](https://github.com/chenhsong/OpenProtocol/blob/master/doc/enums.md#filters).
///
#[derive(
    Debug,
    Ord,
    PartialOrd,
    PartialEq,
    Eq,
    Hash,
    Serialize,
    Deserialize,
    Copy,
    Clone,
    EnumString,
    Display,
    EnumIter,
    AsRefStr,
    IntoStaticStr,
)]
pub enum Filter {
    /// Controller status update messages.
    Status,
    /// Cycle data messages.
    Cycle,
    /// Mold data messages.
    Mold,
    /// Controller action messages.
    Actions,
    /// Controller alarm messages.
    Alarms,
    /// Controller audit messages.
    Audit,
    /// `All` = `Status` + `Cycle` + `Mold` + `Actions` + `Alarms` + `Audit`
    All,
    //
    /// MIS/MES integration: Job scheduling messages.
    JobCards,
    /// MIS/MES integration: User authorization messages.
    Operators,
    //
    /// Industrial bus integration: Connect to the OPC UA server.
    OPCUA,
}

impl Filter {
    /// Returns true if Filter::All.
    #[allow(clippy::trivially_copy_pass_by_ref)]
    pub fn is_all(&self) -> bool {
        *self == Filter::All
    }

    /// Returns true if machine-related filter flags.
    #[allow(clippy::trivially_copy_pass_by_ref)]
    pub fn is_machine(&self) -> bool {
        match self {
            Filter::Status | Filter::Cycle | Filter::Mold | Filter::Actions | Filter::Alarms | Filter::Audit => true,
            _ => false,
        }
    }

    /// Returns true if MIS/MES-related filter flags.
    #[allow(clippy::trivially_copy_pass_by_ref)]
    pub fn is_mis(&self) -> bool {
        match self {
            Filter::JobCards | Filter::Operators => true,
            _ => false,
        }
    }

    /// Returns true if interface of an industrial bus (e.g. OPC UA).
    #[allow(clippy::trivially_copy_pass_by_ref)]
    pub fn is_bus(&self) -> bool {
        match self {
            Filter::OPCUA => true,
            _ => false,
        }
    }
}

impl Filters for [Filter] {
    fn is_all(&self) -> bool {
        // Either Filter::All or all machine filters are present
        self.contains(&Filter::All) || Filter::iter().filter(|f| f.is_machine()).all(|f| self.contains(&f))
    }
}

// Custom serializer and deserializer

static EMPTY_FILTERS: &[Filter] = &[];

pub fn serialize_to_flatten_array<S>(x: &[Filter], s: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    // Streamline filters
    let has_all = x.contains(&Filter::All);
    let fstr: Vec<&str> = x.iter().filter(|f| !has_all || !f.is_machine()).map(|f| f.as_ref()).collect();
    let fstr = fstr.join(", ");

    if fstr.is_empty() {
        s.serialize_str("None")
    } else {
        s.serialize_str(&fstr)
    }
}

pub fn deserialize_flattened_array<'de, D>(d: D) -> Result<Cow<'de, [Filter]>, D::Error>
where
    D: Deserializer<'de>,
{
    let text = String::deserialize(d)?;
    let text = text.trim();

    if text == "None" {
        return Ok(EMPTY_FILTERS.into());
    }

    let mut list: Vec<Filter> = text.split(',').filter_map(|key| Filter::from_str(key.trim()).ok()).collect();

    if list.contains(&Filter::All) {
        // Has All, remove details
        list.retain(|f| !f.is_machine());
    }

    list.dedup();

    if list.is_empty() {
        Ok(EMPTY_FILTERS.into())
    } else {
        Ok(list.into())
    }
}