Skip to main content

nextcloud_ocs/envelope/
status.rs

1use std::fmt;
2
3use serde::de::{self, Deserializer, Visitor};
4use serde::{Serialize, Serializer};
5
6/// Verdict an OCS envelope's `meta` block carries.
7///
8/// Nextcloud reports the verdict twice: as `meta.status` (`"ok"`/`"failure"`)
9/// and as `meta.statuscode`. The numeric code is the authoritative one, since
10/// it distinguishes causes (`997` app not installed, `998` not allowed, `999`
11/// invalid) that the two-valued status cannot express.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum OcsStatus {
14    /// The call succeeded.
15    Ok,
16    /// The server refused the call.
17    Failure,
18}
19
20impl OcsStatus {
21    /// Maps an OCS `statuscode` to its verdict.
22    ///
23    /// `100` and `200` mean success; every other code is a failure.
24    pub const fn from_statuscode(code: u16) -> Self {
25        match code {
26            100 | 200 => Self::Ok,
27            _ => Self::Failure,
28        }
29    }
30
31    /// Parses the `meta.status` text, matched case-insensitively.
32    ///
33    /// An unrecognised value yields `None` rather than a guessed verdict, so a
34    /// renamed status surfaces as a decode failure instead of a wrong answer.
35    pub fn from_status_text(value: &str) -> Option<Self> {
36        match value.trim().to_ascii_lowercase().as_str() {
37            "ok" => Some(Self::Ok),
38            "failure" => Some(Self::Failure),
39            _ => None,
40        }
41    }
42
43    /// Wire representation used in `meta.status`.
44    pub const fn as_str(self) -> &'static str {
45        match self {
46            Self::Ok => "ok",
47            Self::Failure => "failure",
48        }
49    }
50
51    /// Whether this verdict is [`OcsStatus::Ok`].
52    pub const fn is_ok(self) -> bool {
53        matches!(self, Self::Ok)
54    }
55}
56
57impl fmt::Display for OcsStatus {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        f.write_str(self.as_str())
60    }
61}
62
63impl Serialize for OcsStatus {
64    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
65        serializer.serialize_str(self.as_str())
66    }
67}
68
69impl<'de> de::Deserialize<'de> for OcsStatus {
70    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
71        deserializer.deserialize_str(StatusVisitor)
72    }
73}
74
75struct StatusVisitor;
76
77impl Visitor<'_> for StatusVisitor {
78    type Value = OcsStatus;
79
80    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81        formatter.write_str("`ok` or `failure`")
82    }
83
84    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
85        OcsStatus::from_status_text(value)
86            .ok_or_else(|| E::invalid_value(de::Unexpected::Str(value), &self))
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::OcsStatus;
93
94    #[test]
95    fn statuscodes_map_to_a_verdict() {
96        assert_eq!(OcsStatus::from_statuscode(100), OcsStatus::Ok);
97        assert_eq!(OcsStatus::from_statuscode(200), OcsStatus::Ok);
98        assert_eq!(OcsStatus::from_statuscode(997), OcsStatus::Failure);
99        assert_eq!(OcsStatus::from_statuscode(998), OcsStatus::Failure);
100        assert_eq!(OcsStatus::from_statuscode(999), OcsStatus::Failure);
101        assert!(OcsStatus::from_statuscode(200).is_ok());
102    }
103
104    #[test]
105    fn status_text_is_matched_case_insensitively() {
106        assert_eq!(OcsStatus::from_status_text("OK"), Some(OcsStatus::Ok));
107        assert_eq!(
108            OcsStatus::from_status_text("Failure"),
109            Some(OcsStatus::Failure)
110        );
111        assert_eq!(OcsStatus::from_status_text("maybe"), None);
112    }
113
114    #[test]
115    fn deserializes_from_status_text() {
116        let status: OcsStatus = serde_json::from_str("\"ok\"").unwrap();
117        assert_eq!(status, OcsStatus::Ok);
118        assert!(serde_json::from_str::<OcsStatus>("\"unknown\"").is_err());
119    }
120
121    #[test]
122    fn serializes_back_to_status_text() {
123        assert_eq!(
124            serde_json::to_string(&OcsStatus::Failure).unwrap(),
125            "\"failure\""
126        );
127    }
128}