Preview of the proposed up-rust native-frame-model branch (up-rust b6b99c6d, up-spec f0e9b17) — not released documentation. branch · write-up

up_rust/uattributes/
upayloadformat.rs

1/********************************************************************************
2 * Copyright (c) 2023 Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 ********************************************************************************/
13
14use mediatype::MediaType;
15
16use crate::SerializationError;
17
18#[derive(Debug)]
19pub enum UPayloadError {
20    SerializationError(String),
21    MediatypeProblem,
22}
23
24impl UPayloadError {
25    pub fn serialization_error<T>(message: T) -> UPayloadError
26    where
27        T: Into<String>,
28    {
29        Self::SerializationError(message.into())
30    }
31
32    pub fn mediatype_error() -> UPayloadError {
33        Self::MediatypeProblem
34    }
35}
36
37impl std::fmt::Display for UPayloadError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::SerializationError(e) => f.write_fmt(format_args!("Serialization error: {e}")),
41            Self::MediatypeProblem => {
42                f.write_fmt(format_args!("Mediatype problem unsupported or malformed"))
43            }
44        }
45    }
46}
47
48impl std::error::Error for UPayloadError {}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51#[repr(C)]
52/// Payload serialization formats defined by the uProtocol specification.
53pub enum UPayloadFormat {
54    /// No format specified; receivers must not assume a serialization.
55    Unspecified = 0,
56    /// Protobuf message wrapped in `google.protobuf.Any`.
57    ProtobufWrappedInAny = 1,
58    /// Protobuf message bytes.
59    Protobuf = 2,
60    /// JSON text.
61    Json = 3,
62    /// SOME/IP serialized payload.
63    Someip = 4,
64    /// SOME/IP TLV serialized payload.
65    SomeipTlv = 5,
66    /// Raw application-defined bytes.
67    Raw = 6,
68    /// UTF-8 text.
69    Text = 7,
70    /// Shared-memory reference payload.
71    Shm = 8,
72}
73
74impl UPayloadFormat {
75    /// Returns the integer value of the payload format as defined in the protobuf enum.
76    #[must_use]
77    pub fn as_i32(&self) -> i32 {
78        *self as i32
79    }
80
81    /// Converts from the protobuf wire value.
82    ///
83    /// # Errors
84    ///
85    /// Returns an error if `value` is not a defined format.
86    pub fn try_from_i32(value: i32) -> Result<UPayloadFormat, SerializationError> {
87        match value {
88            x if x == UPayloadFormat::Unspecified as i32 => Ok(UPayloadFormat::Unspecified),
89            x if x == UPayloadFormat::ProtobufWrappedInAny as i32 => {
90                Ok(UPayloadFormat::ProtobufWrappedInAny)
91            }
92            x if x == UPayloadFormat::Protobuf as i32 => Ok(UPayloadFormat::Protobuf),
93            x if x == UPayloadFormat::Json as i32 => Ok(UPayloadFormat::Json),
94            x if x == UPayloadFormat::Someip as i32 => Ok(UPayloadFormat::Someip),
95            x if x == UPayloadFormat::SomeipTlv as i32 => Ok(UPayloadFormat::SomeipTlv),
96            x if x == UPayloadFormat::Raw as i32 => Ok(UPayloadFormat::Raw),
97            x if x == UPayloadFormat::Text as i32 => Ok(UPayloadFormat::Text),
98            x if x == UPayloadFormat::Shm as i32 => Ok(UPayloadFormat::Shm),
99            _ => Err(SerializationError::new(format!(
100                "unknown payload format code {value}"
101            ))),
102        }
103    }
104}
105
106impl UPayloadFormat {
107    /// Gets the payload format that corresponds to a given media type.
108    ///
109    /// # Errors
110    ///
111    /// Returns an error if the given string is not a valid media type string or is unsupported by uProtocol.
112    ///
113    /// # Examples
114    ///
115    /// ```rust
116    /// use up_rust::UPayloadFormat;
117    ///
118    /// let parse_attempt = UPayloadFormat::from_media_type("application/json; charset=utf-8");
119    /// assert!(parse_attempt.is_ok_and(|f| f == UPayloadFormat::Json));
120    ///
121    /// let parse_attempt = UPayloadFormat::from_media_type("application/unsupported");
122    /// assert!(parse_attempt.is_err());
123    /// ```
124    pub fn from_media_type(media_type_string: &str) -> Result<Self, UPayloadError> {
125        if let Ok(media_type) = MediaType::parse(media_type_string) {
126            match (media_type.ty.as_str(), media_type.subty.as_str()) {
127                ("application", "json") => return Ok(UPayloadFormat::Json),
128                ("application", "protobuf") => return Ok(UPayloadFormat::Protobuf),
129                ("application", "x-protobuf") => return Ok(UPayloadFormat::ProtobufWrappedInAny),
130                ("application", "octet-stream") => return Ok(UPayloadFormat::Raw),
131                ("application", "x-someip") => return Ok(UPayloadFormat::Someip),
132                ("application", "x-someip_tlv") => return Ok(UPayloadFormat::SomeipTlv),
133                ("text", "plain") => return Ok(UPayloadFormat::Text),
134                ("application", "x-shm") => return Ok(UPayloadFormat::Shm),
135                _ => return Err(UPayloadError::mediatype_error()),
136            }
137        }
138        Err(UPayloadError::mediatype_error())
139    }
140
141    /// Gets the media type corresponding to this payload format.
142    ///
143    /// # Returns
144    ///
145    /// None if the payload format is [`UPayloadFormat::Unspecified`].
146    ///
147    /// # Examples
148    ///
149    /// ```rust
150    /// use up_rust::UPayloadFormat;
151    ///
152    /// assert_eq!(UPayloadFormat::Json.to_media_type().unwrap(), "application/json");
153    /// assert!(UPayloadFormat::Unspecified.to_media_type().is_none());
154    /// ```
155    #[must_use]
156    pub fn to_media_type(self) -> Option<String> {
157        match self {
158            UPayloadFormat::Unspecified => None,
159            UPayloadFormat::ProtobufWrappedInAny => Some("application/x-protobuf".to_string()),
160            UPayloadFormat::Protobuf => Some("application/protobuf".to_string()),
161            UPayloadFormat::Json => Some("application/json".to_string()),
162            UPayloadFormat::Someip => Some("application/x-someip".to_string()),
163            UPayloadFormat::SomeipTlv => Some("application/x-someip_tlv".to_string()),
164            UPayloadFormat::Raw => Some("application/octet-stream".to_string()),
165            UPayloadFormat::Text => Some("text/plain".to_string()),
166            UPayloadFormat::Shm => Some("application/x-shm".to_string()),
167        }
168    }
169}
170
171#[cfg(feature = "up-core-api")]
172mod core_types_support {
173    use super::*;
174    use crate::up_core_api::uattributes::UPayloadFormat as UPayloadFormatProto;
175
176    impl From<UPayloadFormatProto> for UPayloadFormat {
177        fn from(value: UPayloadFormatProto) -> Self {
178            match value {
179                UPayloadFormatProto::UPAYLOAD_FORMAT_UNSPECIFIED => UPayloadFormat::Unspecified,
180                UPayloadFormatProto::UPAYLOAD_FORMAT_JSON => UPayloadFormat::Json,
181                UPayloadFormatProto::UPAYLOAD_FORMAT_PROTOBUF => UPayloadFormat::Protobuf,
182                UPayloadFormatProto::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY => {
183                    UPayloadFormat::ProtobufWrappedInAny
184                }
185                UPayloadFormatProto::UPAYLOAD_FORMAT_RAW => UPayloadFormat::Raw,
186                UPayloadFormatProto::UPAYLOAD_FORMAT_SHM => UPayloadFormat::Shm,
187                UPayloadFormatProto::UPAYLOAD_FORMAT_SOMEIP => UPayloadFormat::Someip,
188                UPayloadFormatProto::UPAYLOAD_FORMAT_SOMEIP_TLV => UPayloadFormat::SomeipTlv,
189                UPayloadFormatProto::UPAYLOAD_FORMAT_TEXT => UPayloadFormat::Text,
190            }
191        }
192    }
193
194    impl From<&UPayloadFormat> for UPayloadFormatProto {
195        fn from(value: &UPayloadFormat) -> Self {
196            match value {
197                UPayloadFormat::Unspecified => UPayloadFormatProto::UPAYLOAD_FORMAT_UNSPECIFIED,
198                UPayloadFormat::Json => UPayloadFormatProto::UPAYLOAD_FORMAT_JSON,
199                UPayloadFormat::Protobuf => UPayloadFormatProto::UPAYLOAD_FORMAT_PROTOBUF,
200                UPayloadFormat::ProtobufWrappedInAny => {
201                    UPayloadFormatProto::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY
202                }
203                UPayloadFormat::Raw => UPayloadFormatProto::UPAYLOAD_FORMAT_RAW,
204                UPayloadFormat::Shm => UPayloadFormatProto::UPAYLOAD_FORMAT_SHM,
205                UPayloadFormat::Someip => UPayloadFormatProto::UPAYLOAD_FORMAT_SOMEIP,
206                UPayloadFormat::SomeipTlv => UPayloadFormatProto::UPAYLOAD_FORMAT_SOMEIP_TLV,
207                UPayloadFormat::Text => UPayloadFormatProto::UPAYLOAD_FORMAT_TEXT,
208            }
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    use test_case::test_case;
218
219    #[test_case("application/json", Ok(UPayloadFormat::Json); "map from JSON")]
220    #[test_case(
221        "application/json; charset=utf-8",
222        Ok(UPayloadFormat::Json);
223        "map from JSON with parameter"
224    )]
225    #[test_case("application/protobuf", Ok(UPayloadFormat::Protobuf); "map from PROTOBUF")]
226    #[test_case(
227        "application/x-protobuf",
228        Ok(UPayloadFormat::ProtobufWrappedInAny); "map from PROTOBUF_WRAPPED"
229    )]
230    #[test_case("application/octet-stream", Ok(UPayloadFormat::Raw); "map from RAW")]
231    #[test_case("application/x-someip", Ok(UPayloadFormat::Someip); "map from SOMEIP")]
232    #[test_case(
233        "application/x-someip_tlv",
234        Ok(UPayloadFormat::SomeipTlv); "map from SOMEIP_TLV"
235    )]
236    #[test_case("text/plain", Ok(UPayloadFormat::Text); "map from TEXT")]
237    #[test_case("application/unsupported; foo=bar", Err(UPayloadError::mediatype_error()); "fail for unsupported media type")]
238    fn test_from_media_type(
239        media_type: &str,
240        expected_format: Result<UPayloadFormat, UPayloadError>,
241    ) {
242        let parsing_result = UPayloadFormat::from_media_type(media_type);
243        assert!(parsing_result.is_ok() == expected_format.is_ok());
244        if let Ok(format) = expected_format {
245            assert_eq!(format, parsing_result.unwrap());
246        }
247    }
248
249    #[test_case(UPayloadFormat::Json, Some("application/json".to_string()); "map JSON format to media type")]
250    #[test_case(UPayloadFormat::Protobuf, Some("application/protobuf".to_string()); "map PROTOBUF format to media type")]
251    #[test_case(UPayloadFormat::ProtobufWrappedInAny, Some("application/x-protobuf".to_string()); "map PROTOBUF_WRAPPED format to media type")]
252    #[test_case(UPayloadFormat::Raw, Some("application/octet-stream".to_string()); "map RAW format to media type")]
253    #[test_case(UPayloadFormat::Shm, Some("application/x-shm".to_string()); "map SHM format to media type")]
254    #[test_case(UPayloadFormat::Someip, Some("application/x-someip".to_string()); "map SOMEIP format to media type")]
255    #[test_case(UPayloadFormat::SomeipTlv, Some("application/x-someip_tlv".to_string()); "map SOMEIP_TLV format to media type")]
256    #[test_case(UPayloadFormat::Text, Some("text/plain".to_string()); "map TEXT format to media type")]
257    #[test_case(UPayloadFormat::Unspecified, None; "map UNSPECIFIED format to None")]
258    fn test_to_media_type(format: UPayloadFormat, expected_media_type: Option<String>) {
259        assert_eq!(format.to_media_type(), expected_media_type);
260    }
261}