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

up_rust/
ustatus.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 std::error::Error;
15
16use bytes::Bytes;
17
18use crate::SerializationError;
19
20/// Canonical status codes for uProtocol operations.
21///
22/// These mirror the gRPC canonical codes, as required by the uProtocol
23/// specification; every transport and role reports failures through
24/// them. The [application guide](crate::guide::applications) shows how
25/// callers act on the common ones.
26#[derive(Copy, Debug, Clone, PartialEq)]
27#[repr(C)]
28pub enum UCode {
29    /// Not an error; the operation completed successfully.
30    Ok = 0,
31    /// The operation was cancelled, typically by the caller.
32    Cancelled = 1,
33    /// Unknown error, or an error from a foreign error space.
34    Unknown = 2,
35    /// The caller specified an invalid argument, regardless of system state.
36    InvalidArgument = 3,
37    /// A deadline expired before the operation could complete.
38    DeadlineExceeded = 4,
39    /// A requested entity (listener, topic, resource) was not found.
40    NotFound = 5,
41    /// The entity a caller attempted to create already exists.
42    AlreadyExists = 6,
43    /// The caller lacks permission to execute the operation.
44    PermissionDenied = 7,
45    /// A resource (quota, buffer space, loan capacity) has been exhausted.
46    ResourceExhausted = 8,
47    /// The system is not in a state required for the operation.
48    FailedPrecondition = 9,
49    /// The operation was aborted, typically due to a concurrency conflict.
50    Aborted = 10,
51    /// The operation was attempted past the valid range.
52    OutOfRange = 11,
53    /// The operation is not implemented or supported by this transport or service.
54    Unimplemented = 12,
55    /// An internal invariant was broken; a bug in the implementation.
56    Internal = 13,
57    /// The service or link is currently unavailable; retrying may succeed.
58    Unavailable = 14,
59    /// Unrecoverable data loss or corruption.
60    DataLoss = 15,
61    /// The request lacks valid authentication credentials.
62    Unauthenticated = 16,
63}
64
65impl UCode {
66    /// Converts from the protobuf wire value.
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if `value` is not a defined code.
71    pub fn try_from_i32(value: i32) -> Result<Self, SerializationError> {
72        match value {
73            x if x == UCode::Ok as i32 => Ok(UCode::Ok),
74            x if x == UCode::Cancelled as i32 => Ok(UCode::Cancelled),
75            x if x == UCode::Unknown as i32 => Ok(UCode::Unknown),
76            x if x == UCode::InvalidArgument as i32 => Ok(UCode::InvalidArgument),
77            x if x == UCode::DeadlineExceeded as i32 => Ok(UCode::DeadlineExceeded),
78            x if x == UCode::NotFound as i32 => Ok(UCode::NotFound),
79            x if x == UCode::AlreadyExists as i32 => Ok(UCode::AlreadyExists),
80            x if x == UCode::PermissionDenied as i32 => Ok(UCode::PermissionDenied),
81            x if x == UCode::ResourceExhausted as i32 => Ok(UCode::ResourceExhausted),
82            x if x == UCode::FailedPrecondition as i32 => Ok(UCode::FailedPrecondition),
83            x if x == UCode::Aborted as i32 => Ok(UCode::Aborted),
84            x if x == UCode::OutOfRange as i32 => Ok(UCode::OutOfRange),
85            x if x == UCode::Unimplemented as i32 => Ok(UCode::Unimplemented),
86            x if x == UCode::Internal as i32 => Ok(UCode::Internal),
87            x if x == UCode::Unavailable as i32 => Ok(UCode::Unavailable),
88            x if x == UCode::DataLoss as i32 => Ok(UCode::DataLoss),
89            x if x == UCode::Unauthenticated as i32 => Ok(UCode::Unauthenticated),
90            _ => Err(SerializationError::new(format!(
91                "unknown UCode value: {}",
92                value
93            ))),
94        }
95    }
96
97    #[must_use]
98    /// Returns the protobuf wire value of this code.
99    pub fn value(&self) -> i32 {
100        *self as i32
101    }
102}
103
104#[derive(Debug, Clone, PartialEq)]
105#[repr(C)]
106/// Minimal `google.protobuf.Any` carrier: a type URL plus serialized bytes.
107pub struct UAny {
108    type_url: String,
109    value: Bytes,
110}
111
112impl UAny {
113    /// Creates a carrier from a type URL and serialized value bytes.
114    pub fn new<V: Into<Bytes>>(type_url: String, value: V) -> Self {
115        UAny {
116            type_url,
117            value: value.into(),
118        }
119    }
120
121    /// Returns the type URL identifying the payload type.
122    pub fn type_url(&self) -> &str {
123        &self.type_url
124    }
125    /// Deprecated alias for [`Self::type_url`].
126    #[deprecated(since = "0.11.0", note = "renamed to `type_url`")]
127    pub fn get_type_url(&self) -> &str {
128        self.type_url()
129    }
130
131    /// Returns the serialized value bytes.
132    pub fn value(&self) -> &[u8] {
133        &self.value
134    }
135    /// Deprecated alias for [`Self::value`].
136    #[deprecated(since = "0.11.0", note = "renamed to `value`")]
137    pub fn get_value(&self) -> &[u8] {
138        self.value()
139    }
140}
141
142#[derive(Debug, Clone, PartialEq)]
143#[repr(C)]
144/// Operation status: a [`UCode`] with optional message and details.
145pub struct UStatus {
146    code: UCode,
147    message: Option<String>,
148    details: Vec<UAny>,
149}
150
151impl UStatus {
152    /// Creates a status representing a success.
153    ///
154    /// # Examples
155    ///
156    /// ```rust
157    /// use up_rust::{UCode, UStatus};
158    ///
159    /// let status = UStatus::ok();
160    /// assert_eq!(status.code(), UCode::Ok);
161    /// ```
162    #[must_use]
163    pub fn ok() -> Self {
164        UStatus {
165            code: UCode::Ok,
166            message: None,
167            details: vec![],
168        }
169    }
170
171    /// Creates a status representing a failure.
172    ///
173    /// # Examples
174    ///
175    /// ```rust
176    /// use up_rust::{UCode, UStatus};
177    ///
178    /// let status = UStatus::fail_with_code(UCode::DataLoss, "something went wrong");
179    /// assert_eq!(status.code(), UCode::DataLoss);
180    /// assert_eq!(status.message().unwrap(), "something went wrong");
181    /// ```
182    pub fn fail_with_code<M: Into<std::string::String>>(code: UCode, msg: M) -> Self {
183        Self::new(code, Some(msg), None)
184    }
185
186    /// Creates a status from a code and an optional message.
187    ///
188    /// # Examples
189    ///
190    /// ```rust
191    /// use up_rust::{UCode, UStatus};
192    ///
193    /// let status = UStatus::new(UCode::DataLoss, Some("something went wrong"), None);
194    /// assert_eq!(status.code(), UCode::DataLoss);
195    /// assert_eq!(status.message().unwrap(), "something went wrong");
196    /// assert!(status.details().is_empty());
197    /// ```
198    pub fn new<M: Into<std::string::String>>(
199        code: UCode,
200        msg: Option<M>,
201        details: Option<Vec<UAny>>,
202    ) -> Self {
203        UStatus {
204            code,
205            message: msg.map(|m| m.into()),
206            details: details.unwrap_or_default(),
207        }
208    }
209
210    /// Checks if this status represents a failure.
211    ///
212    /// # Examples
213    ///
214    /// ```rust
215    /// use up_rust::{UCode, UStatus};
216    ///
217    /// let failed_status = UStatus::fail_with_code(UCode::Internal, "something went wrong");
218    /// assert!(failed_status.is_failed());
219    ///
220    /// let succeeded_status = UStatus::ok();
221    /// assert!(!succeeded_status.is_failed());
222    /// ```
223    #[must_use]
224    pub fn is_failed(&self) -> bool {
225        self.code() != UCode::Ok
226    }
227
228    /// Checks if this status represents a success.
229    ///
230    /// # Examples
231    ///
232    /// ```rust
233    /// use up_rust::{UCode, UStatus};
234    ///
235    /// let succeeded_status = UStatus::ok();
236    /// assert!(succeeded_status.is_success());
237    ///
238    /// let failed_status = UStatus::fail_with_code(UCode::Internal, "something went wrong");
239    /// assert!(!failed_status.is_success());
240    /// ```
241    #[must_use]
242    pub fn is_success(&self) -> bool {
243        self.code() == UCode::Ok
244    }
245
246    /// Gets this status' error message.
247    ///
248    /// # Returns
249    ///
250    /// an empty string if this instance has been created without a message.
251    ///
252    /// # Examples
253    ///
254    /// ```rust
255    /// use up_rust::{UCode, UStatus};
256    ///
257    /// let failed_status = UStatus::fail_with_code(UCode::Internal, "my error message");
258    /// assert_eq!(failed_status.message(), Some("my error message"));
259    ///
260    /// let succeeded_status = UStatus::ok();
261    /// assert!(succeeded_status.message().is_none());
262    /// ```
263    #[must_use]
264    pub fn message(&self) -> Option<&str> {
265        self.message.as_deref()
266    }
267    /// Deprecated alias for [`Self::message`].
268    #[deprecated(since = "0.11.0", note = "renamed to `message`")]
269    pub fn get_message(&self) -> Option<&str> {
270        self.message()
271    }
272
273    /// Gets this status' error message or a default value if none is set.
274    ///
275    /// # Arguments
276    ///
277    /// * `default` - The default value to return if this status has no message.
278    ///
279    /// # Returns
280    ///
281    /// the error message if set, otherwise the provided default value.
282    ///
283    /// # Examples
284    ///
285    /// ```rust
286    /// use up_rust::{UCode, UStatus};
287    ///
288    /// let failed_status = UStatus::fail_with_code(UCode::Internal, "my error message");
289    /// assert_eq!(failed_status.message_or_default("default"), "my error message");
290    ///
291    /// let succeeded_status = UStatus::ok();
292    /// assert_eq!(succeeded_status.message_or_default("default"), "default");
293    /// ```
294    #[must_use]
295    pub fn message_or_default<'a>(&'a self, default: &'a str) -> &'a str {
296        self.message().unwrap_or(default)
297    }
298    /// Deprecated alias for [`Self::message_or_default`].
299    #[deprecated(since = "0.11.0", note = "renamed to `message_or_default`")]
300    pub fn get_message_or_default<'a>(&'a self, default: &'a str) -> &'a str {
301        self.message_or_default(default)
302    }
303
304    /// Gets this status' error code.
305    ///
306    /// # Examples
307    ///
308    /// ```rust
309    /// use up_rust::{UCode, UStatus};
310    ///
311    /// let status_with_code = UStatus::fail_with_code(UCode::Internal, "my error message");
312    /// assert_eq!(status_with_code.code(), UCode::Internal);
313    /// ```
314    #[must_use]
315    pub fn code(&self) -> UCode {
316        self.code
317    }
318    /// Deprecated alias for [`Self::code`].
319    #[deprecated(since = "0.11.0", note = "renamed to `code`")]
320    pub fn get_code(&self) -> UCode {
321        self.code()
322    }
323
324    /// Gets this status' details.
325    ///
326    /// # Examples
327    ///
328    /// ```rust
329    /// use up_rust::{UAny, UCode, UStatus};
330    ///
331    /// let details = vec![UAny::new("type.googleapis.com/google.protobuf.StringValue".to_string(), b"the string".as_ref())];
332    /// let status_with_details = UStatus::new(UCode::Internal, Some("my error message"), Some(details.clone()));
333    /// assert_eq!(status_with_details.details(), details.as_slice());
334    ///
335    /// let status_without_details = UStatus::fail_with_code(UCode::Internal, "my error message");
336    /// assert!(status_without_details.details().is_empty());
337    /// ```
338    #[must_use]
339    pub fn details(&self) -> &[UAny] {
340        &self.details
341    }
342    /// Deprecated alias for [`Self::details`].
343    #[deprecated(since = "0.11.0", note = "renamed to `details`")]
344    pub fn get_details(&self) -> &[UAny] {
345        self.details()
346    }
347}
348
349impl std::fmt::Display for UStatus {
350    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351        let mut s = String::from("UStatus [");
352        s.push_str(&format!("code: {:?}", self.code()));
353        if let Some(msg) = self.message() {
354            s.push_str(&format!(", message: {msg}"));
355        }
356        if !self.details().is_empty() {
357            s.push_str(&format!(", details: {:?}", self.details()));
358        }
359        s.push(']');
360        write!(f, "{}", s)
361    }
362}
363
364impl Error for UStatus {}
365
366#[cfg(feature = "up-core-api")]
367mod core_types_support {
368    use protobuf::{well_known_types::any::Any, Message};
369
370    use super::*;
371
372    use crate::up_core_api::{ucode::UCode as UCodeProto, ustatus::UStatus as UStatusProto};
373    use crate::{ProtobufMappable, SerializationError};
374
375    impl From<UCodeProto> for UCode {
376        fn from(ucode_proto: UCodeProto) -> Self {
377            match ucode_proto {
378                UCodeProto::OK => UCode::Ok,
379                UCodeProto::CANCELLED => UCode::Cancelled,
380                UCodeProto::UNKNOWN => UCode::Unknown,
381                UCodeProto::INVALID_ARGUMENT => UCode::InvalidArgument,
382                UCodeProto::DEADLINE_EXCEEDED => UCode::DeadlineExceeded,
383                UCodeProto::NOT_FOUND => UCode::NotFound,
384                UCodeProto::ALREADY_EXISTS => UCode::AlreadyExists,
385                UCodeProto::PERMISSION_DENIED => UCode::PermissionDenied,
386                UCodeProto::RESOURCE_EXHAUSTED => UCode::ResourceExhausted,
387                UCodeProto::FAILED_PRECONDITION => UCode::FailedPrecondition,
388                UCodeProto::ABORTED => UCode::Aborted,
389                UCodeProto::OUT_OF_RANGE => UCode::OutOfRange,
390                UCodeProto::UNIMPLEMENTED => UCode::Unimplemented,
391                UCodeProto::INTERNAL => UCode::Internal,
392                UCodeProto::UNAVAILABLE => UCode::Unavailable,
393                UCodeProto::DATA_LOSS => UCode::DataLoss,
394                UCodeProto::UNAUTHENTICATED => UCode::Unauthenticated,
395            }
396        }
397    }
398
399    impl From<UCode> for UCodeProto {
400        fn from(ucode: UCode) -> Self {
401            match ucode {
402                UCode::Ok => UCodeProto::OK,
403                UCode::Cancelled => UCodeProto::CANCELLED,
404                UCode::Unknown => UCodeProto::UNKNOWN,
405                UCode::InvalidArgument => UCodeProto::INVALID_ARGUMENT,
406                UCode::DeadlineExceeded => UCodeProto::DEADLINE_EXCEEDED,
407                UCode::NotFound => UCodeProto::NOT_FOUND,
408                UCode::AlreadyExists => UCodeProto::ALREADY_EXISTS,
409                UCode::PermissionDenied => UCodeProto::PERMISSION_DENIED,
410                UCode::ResourceExhausted => UCodeProto::RESOURCE_EXHAUSTED,
411                UCode::FailedPrecondition => UCodeProto::FAILED_PRECONDITION,
412                UCode::Aborted => UCodeProto::ABORTED,
413                UCode::OutOfRange => UCodeProto::OUT_OF_RANGE,
414                UCode::Unimplemented => UCodeProto::UNIMPLEMENTED,
415                UCode::Internal => UCodeProto::INTERNAL,
416                UCode::Unavailable => UCodeProto::UNAVAILABLE,
417                UCode::DataLoss => UCodeProto::DATA_LOSS,
418                UCode::Unauthenticated => UCodeProto::UNAUTHENTICATED,
419            }
420        }
421    }
422
423    impl From<Any> for UAny {
424        fn from(value: Any) -> Self {
425            UAny {
426                type_url: value.type_url,
427                value: value.value.into(),
428            }
429        }
430    }
431
432    impl From<&UAny> for Any {
433        fn from(value: &UAny) -> Self {
434            Any {
435                type_url: value.type_url.clone(),
436                value: value.value.clone().into(),
437                ..Default::default()
438            }
439        }
440    }
441
442    impl TryFrom<UStatusProto> for UStatus {
443        type Error = SerializationError;
444
445        fn try_from(status_proto: UStatusProto) -> Result<Self, Self::Error> {
446            // an unsupported UCode value is considered a serialization error because we cannot
447            // simply map it to OK (value 0) or UNKNOWN (value 2) without risking data loss
448            let code = status_proto
449                .code
450                .enum_value()
451                .map_err(|e| SerializationError::new(format!("unsupported UCode {e}")))
452                .map(UCode::from)?;
453            let details = status_proto
454                .details
455                .into_iter()
456                .map(UAny::from)
457                .collect::<Vec<_>>();
458            Ok(UStatus::new(code, status_proto.message, details.into()))
459        }
460    }
461
462    impl From<&UStatus> for UStatusProto {
463        fn from(value: &UStatus) -> Self {
464            UStatusProto {
465                code: UCodeProto::from(value.code()).into(),
466                message: value.message().map(|m| m.to_string()),
467                details: value.details().iter().map(Any::from).collect(),
468                ..Default::default()
469            }
470        }
471    }
472
473    impl ProtobufMappable for UStatus {
474        fn parse_from_protobuf_bytes(proto: &[u8]) -> Result<Self, SerializationError> {
475            let proto = UStatusProto::parse_from_bytes(proto)?;
476            UStatus::try_from(proto)
477        }
478
479        fn parse_from_packed_protobuf_bytes(proto: &[u8]) -> Result<Self, SerializationError> {
480            let any = Any::parse_from_bytes(proto)?;
481            match any.unpack::<UStatusProto>() {
482                Ok(Some(v)) => UStatus::try_from(v),
483                Ok(None) => Err(SerializationError::new(
484                    "cannot unpack UStatus, type mismatch",
485                )),
486                Err(e) => Err(SerializationError::from(e)),
487            }
488        }
489
490        fn write_to_protobuf_bytes(&self) -> Result<Vec<u8>, SerializationError> {
491            UStatusProto::from(self)
492                .write_to_bytes()
493                .map_err(SerializationError::from)
494        }
495
496        fn write_to_packed_protobuf_bytes(&self) -> Result<Vec<u8>, SerializationError> {
497            Any::pack(&UStatusProto::from(self))
498                .map_err(SerializationError::from)
499                .and_then(|any| any.write_to_protobuf_bytes())
500        }
501    }
502}
503
504#[cfg(test)]
505mod tests {
506    use super::*;
507
508    const ALL_UCODES: &[UCode] = &[
509        UCode::Ok,
510        UCode::Cancelled,
511        UCode::Unknown,
512        UCode::InvalidArgument,
513        UCode::DeadlineExceeded,
514        UCode::NotFound,
515        UCode::AlreadyExists,
516        UCode::PermissionDenied,
517        UCode::Unauthenticated,
518        UCode::ResourceExhausted,
519        UCode::FailedPrecondition,
520        UCode::Aborted,
521        UCode::OutOfRange,
522        UCode::Unimplemented,
523        UCode::Internal,
524        UCode::Unavailable,
525        UCode::DataLoss,
526    ];
527
528    #[test]
529    // [utest->req~ustatus-data-model-impl~1]
530    fn test_ustatus_fail_with_code() {
531        for code in ALL_UCODES {
532            let ustatus = UStatus::fail_with_code(*code, "the message");
533            assert!(ustatus.code() == *code && ustatus.message() == Some("the message"));
534        }
535    }
536
537    #[test]
538    // [utest->req~ustatus-data-model-proto~1]
539    #[cfg(feature = "up-core-api")]
540    fn test_proto_serialization() {
541        use crate::ProtobufMappable;
542
543        let ustatus = UStatus::fail_with_code(UCode::Cancelled, "the message");
544        let proto = ustatus
545            .write_to_protobuf_bytes()
546            .expect("failed to serialize to protobuf");
547        let deserialized_status = UStatus::parse_from_protobuf_bytes(proto.as_slice())
548            .expect("failed to deserialize protobuf");
549        assert_eq!(ustatus, deserialized_status);
550    }
551
552    #[test]
553    fn test_is_failed() {
554        assert!(!UStatus::ok().is_failed());
555        for code in ALL_UCODES {
556            let ustatus = UStatus::fail_with_code(*code, "the message");
557            assert_eq!(ustatus.is_failed(), *code != UCode::Ok);
558        }
559    }
560
561    #[test]
562    fn test_is_success() {
563        assert!(UStatus::ok().is_success());
564        for code in ALL_UCODES {
565            let ustatus = UStatus::fail_with_code(*code, "the message");
566            assert_eq!(ustatus.is_success(), *code == UCode::Ok);
567        }
568    }
569}