azure_iot_operations_protocol/
rpc_command.rs

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
145
146
147
148
149
150
151
152
153
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Envoys for Remote Procedure Call (RPC) operations.

use std::str::FromStr;

use crate::ProtocolVersion;
use crate::common::aio_protocol_error::AIOProtocolError;

/// This module contains the command invoker implementation.
pub mod invoker;

/// This module contains the command executor implementation.
pub mod executor;

/// Re-export the command invoker and executor for ease of use.
pub use executor::Executor;
pub use invoker::Invoker;

/// Protocol version used by all command envoys in this module
pub(crate) const RPC_COMMAND_PROTOCOL_VERSION: ProtocolVersion =
    ProtocolVersion { major: 1, minor: 0 };
/// Assumed version if no version is provided.
pub(crate) const DEFAULT_RPC_COMMAND_PROTOCOL_VERSION: ProtocolVersion =
    ProtocolVersion { major: 1, minor: 0 };

/// Represents the valid status codes for command responses.
#[repr(u16)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum StatusCode {
    /// No error.
    Ok = 200,

    /// There is no content to send for this response.
    NoContent = 204,

    /// Header or payload is missing or invalid.
    BadRequest = 400,

    /// The request timed out before a response could be received from the command processor.
    RequestTimeout = 408,

    /// The content type specified in the request is not supported by this implementation.
    UnsupportedMediaType = 415,

    /// Unknown error, internal logic error, or command processor error.
    InternalServerError = 500,

    /// Invalid service state preventing command from executing properly.
    ServiceUnavailable = 503,
    /// The request failed because the remote party did not support the requested protocol version.
    VersionNotSupported = 505,
}

impl FromStr for StatusCode {
    type Err = AIOProtocolError;

    fn from_str(s: &str) -> Result<Self, AIOProtocolError> {
        match s.parse::<u16>() {
            Ok(status) => match status {
                x if x == StatusCode::Ok as u16 => Ok(StatusCode::Ok),
                x if x == StatusCode::NoContent as u16 => Ok(StatusCode::NoContent),
                x if x == StatusCode::BadRequest as u16 => Ok(StatusCode::BadRequest),
                x if x == StatusCode::RequestTimeout as u16 => Ok(StatusCode::RequestTimeout),
                x if x == StatusCode::UnsupportedMediaType as u16 => {
                    Ok(StatusCode::UnsupportedMediaType)
                }
                x if x == StatusCode::InternalServerError as u16 => {
                    Ok(StatusCode::InternalServerError)
                }
                x if x == StatusCode::ServiceUnavailable as u16 => {
                    Ok(StatusCode::ServiceUnavailable)
                }
                x if x == StatusCode::VersionNotSupported as u16 => {
                    Ok(StatusCode::VersionNotSupported)
                }
                _ => Err(AIOProtocolError::new_unknown_error(
                    true,
                    false,
                    None,
                    Some(format!("Unknown status code: {s}")),
                    None,
                )),
            },
            Err(e) => Err(AIOProtocolError::new_header_invalid_error(
                "__stat",
                s,
                false,
                Some(format!(
                    "Could not parse status in response '{s}' as an integer: {e}"
                )),
                None,
            )),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use test_case::test_case;

    use crate::{common::aio_protocol_error::AIOProtocolErrorKind, rpc_command::StatusCode};

    #[test_case(StatusCode::Ok; "Ok")]
    #[test_case(StatusCode::NoContent; "NoContent")]
    #[test_case(StatusCode::BadRequest; "BadRequest")]
    #[test_case(StatusCode::RequestTimeout; "RequestTimeout")]
    #[test_case(StatusCode::UnsupportedMediaType; "UnsupportedMediaType")]
    #[test_case(StatusCode::InternalServerError; "InternalServerError")]
    #[test_case(StatusCode::ServiceUnavailable; "ServiceUnavailable")]
    #[test_case(StatusCode::VersionNotSupported; "VersionNotSupported")]
    fn test_to_from_string(status_code: StatusCode) {
        assert_eq!(
            status_code,
            StatusCode::from_str(&(status_code as u16).to_string()).unwrap()
        );
    }

    #[test]
    fn test_invalid_status_code() {
        let test_invalid_code = "not a number";
        let code_result = StatusCode::from_str(test_invalid_code);
        match code_result {
            Ok(_) => panic!("Expected error"),
            Err(e) => {
                assert_eq!(e.kind, AIOProtocolErrorKind::HeaderInvalid);
                assert!(!e.is_shallow);
                assert!(!e.is_remote);
                assert!(e.nested_error.is_none());
                assert_eq!(e.header_name, Some("__stat".to_string()));
                assert_eq!(e.header_value, Some(test_invalid_code.to_string()));
            }
        }
    }

    #[test]
    fn test_unknown_status_code() {
        let test_unknown_code = 201;
        let code_result = StatusCode::from_str(&test_unknown_code.to_string());
        match code_result {
            Ok(_) => panic!("Expected error"),
            Err(e) => {
                assert_eq!(e.kind, AIOProtocolErrorKind::UnknownError);
                assert!(!e.is_shallow);
                assert!(e.is_remote);
                assert!(e.nested_error.is_none());
            }
        }
    }
}