Skip to content

Commit 3ef9400

Browse files
authored
[#82] Add USubscription trait
Added a dedicated USubscription trait to get going with uStreamer ability to forward Publish messages. * Trait methods are documented * Trait is documented with prose description and doc examples * Moved USubscription and related to core::usubscription * Shortened / hid more of USubscription example * Made USubscription trait Send + Sync trait bound * Moved UDiscovery and UTwin items under core::udiscovery and core::utwin respectively * Updating signatures of USubscription client object to reflect exceptional path when e.g. we are unable to communicate with the USubscription service and/or arguments given are malformed. * Callers of USubscription::subscribe() for example, would still need to review the 'happy' path when returned an Ok(SubscriptionResponse) because contained therein could be a UCode which is not UCode::OK. Fixes #82
1 parent 9efa980 commit 3ef9400

File tree

6 files changed

+325
-7
lines changed

6 files changed

+325
-7
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ cloudevents = ["dep:cloudevents-sdk", "dep:serde", "dep:serde_json"]
3737
udiscovery = []
3838
usubscription = []
3939
utwin = []
40+
default = []
4041

4142
[dependencies]
4243
async-trait = { version = "0.1" }

src/core.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#[cfg(feature = "usubscription")]
2+
pub mod usubscription;
3+
4+
// Types not used by up_rust, but re-exported to up_rust users, keeping them in their respective submodules
5+
#[cfg(feature = "udiscovery")]
6+
pub mod udiscovery;
7+
#[cfg(feature = "utwin")]
8+
pub mod utwin;

src/core/udiscovery.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub use crate::up_core_api::udiscovery::{
2+
node::Type, notification::Operation, notification::Resources, AddNodesRequest,
3+
DeleteNodesRequest, FindNodesRequest, FindNodesResponse, LookupUriResponse, Node,
4+
NodeNotificationTopic, Notification, NotificationsRequest, ObserverInfo, PropertyValue,
5+
UpdateNodeRequest, UpdatePropertyRequest,
6+
};

src/core/usubscription.rs

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
pub use crate::up_core_api::usubscription::{
2+
subscription_status::State, EventDeliveryConfig, FetchSubscribersRequest,
3+
FetchSubscriptionsRequest, FetchSubscriptionsResponse, NotificationsRequest,
4+
SubscribeAttributes, SubscriberInfo, SubscriptionRequest, SubscriptionResponse,
5+
SubscriptionStatus, UnsubscribeRequest,
6+
};
7+
use crate::UStatus;
8+
use async_trait::async_trait;
9+
10+
/// `USubscription` is the uP-L3 client interface to the uSubscription service.
11+
///
12+
/// A client would use a concrete implementation of `USubscription` typically to subscribe to
13+
/// a topic of interest and then unsubscribe when finished.
14+
///
15+
/// Implementations of `USubscription` can be transport-specific to allow for flexibility and optimizations.
16+
///
17+
/// # Examples
18+
///
19+
/// ## Typical usage
20+
///
21+
/// ```
22+
/// # use async_trait::async_trait;
23+
/// # use std::future::Future;
24+
/// # use up_rust::{UMessage, Number, UAuthority, UEntity, UResource, UStatus, UUri, UListener};
25+
/// #
26+
/// # mod up_client_foo {
27+
/// # use std::sync::Arc;
28+
/// # use up_rust::{UTransport, UListener, UStatus, UMessage, UUri};
29+
/// # use async_trait::async_trait;
30+
/// # pub struct UPClientFoo;
31+
/// #
32+
/// # #[async_trait]
33+
/// # impl UTransport for UPClientFoo {
34+
/// # async fn send(&self, message: UMessage) -> Result<(), UStatus> {
35+
/// # todo!()
36+
/// # }
37+
/// #
38+
/// # async fn receive(&self, topic: UUri) -> Result<UMessage, UStatus> {
39+
/// # todo!()
40+
/// # }
41+
/// #
42+
/// # async fn register_listener(&self, topic: UUri, listener: Arc<dyn UListener>) -> Result<(), UStatus> {
43+
/// # Ok(())
44+
/// # }
45+
/// #
46+
/// # async fn unregister_listener(&self, topic: UUri, listener: Arc<dyn UListener>) -> Result<(), UStatus> {
47+
/// # Ok(())
48+
/// # }
49+
/// # }
50+
/// #
51+
/// # impl UPClientFoo {
52+
/// # pub fn new() -> Self {
53+
/// # Self
54+
/// # }
55+
/// # }
56+
/// # }
57+
/// #
58+
/// # mod usubscription_foo {
59+
/// # use async_trait::async_trait;
60+
/// # use protobuf::EnumOrUnknown;
61+
/// # use up_rust::{UStatus, UCode,
62+
/// # core::usubscription::{USubscription, FetchSubscribersRequest, FetchSubscriptionsRequest,
63+
/// # FetchSubscriptionsResponse, NotificationsRequest, SubscriptionRequest,
64+
/// # SubscriptionResponse, UnsubscribeRequest, SubscriptionStatus, State,
65+
/// # EventDeliveryConfig},
66+
/// # };
67+
/// #
68+
/// # pub struct USubscriptionFoo;
69+
/// #
70+
/// # #[async_trait]
71+
/// # impl USubscription for USubscriptionFoo {
72+
/// # async fn subscribe(&self, subscription_request: SubscriptionRequest) -> Result<SubscriptionResponse, UStatus> {
73+
/// # let subscription_status = SubscriptionStatus {
74+
/// # state: EnumOrUnknown::from(State::SUBSCRIBED),
75+
/// # code: EnumOrUnknown::from(UCode::OK),
76+
/// # message: "Subscription success".to_string(),
77+
/// # ..Default::default()
78+
/// # };
79+
/// #
80+
/// # let event_delivery_config = EventDeliveryConfig {
81+
/// # id: "SUBSCRIPTION_TOPIC".to_string(),
82+
/// # type_: "Foo/Vehicle/EventHubs".to_string(),
83+
/// # attributes: Default::default(),
84+
/// # ..Default::default()
85+
/// # };
86+
/// #
87+
/// # let subscription_response = SubscriptionResponse {
88+
/// # status: Some(subscription_status).into(),
89+
/// # config: Default::default(),
90+
/// # topic: Default::default(),
91+
/// # ..Default::default()
92+
/// # };
93+
/// #
94+
/// # Ok(subscription_response)
95+
/// # }
96+
/// #
97+
/// # async fn unsubscribe(&self, unsubscribe_request: UnsubscribeRequest) -> Result<(), UStatus> {
98+
/// # Ok(())
99+
/// # }
100+
/// #
101+
/// # async fn fetch_subscriptions(&self, fetch_subscriptions_request: FetchSubscriptionsRequest) -> Result<FetchSubscriptionsResponse, UStatus> {
102+
/// # todo!()
103+
/// # }
104+
/// #
105+
/// # async fn register_for_notifications(&self, notifications_request: NotificationsRequest) -> Result<(), UStatus> {
106+
/// # todo!()
107+
/// # }
108+
/// #
109+
/// # async fn unregister_for_notifications(&self, notifications_request: NotificationsRequest) -> Result<(), UStatus> {
110+
/// # todo!()
111+
/// # }
112+
/// #
113+
/// # async fn fetch_subscribers(&self, fetch_subscribers_request: FetchSubscribersRequest) -> Result<FetchSubscriptionsResponse, UStatus> {
114+
/// # todo!()
115+
/// # }
116+
/// # }
117+
/// #
118+
/// # impl USubscriptionFoo {
119+
/// # pub fn new() -> Self {
120+
/// # Self
121+
/// # }
122+
/// # }
123+
/// # }
124+
/// #
125+
/// # #[derive(Clone)]
126+
/// # pub struct MyListener;
127+
/// #
128+
/// # #[async_trait]
129+
/// # impl UListener for MyListener {
130+
/// # async fn on_receive(&self, msg: UMessage) {
131+
/// # todo!()
132+
/// # }
133+
/// #
134+
/// # async fn on_error(&self, err: UStatus) {
135+
/// # todo!()
136+
/// # }
137+
/// #
138+
/// # }
139+
/// #
140+
/// # impl MyListener {
141+
/// # pub fn new() -> Self {
142+
/// # Self
143+
/// # }
144+
/// # }
145+
/// #
146+
/// # #[async_std::main]
147+
/// # pub async fn main() -> Result<(), UStatus> {
148+
/// #
149+
/// # let my_uuri = Default::default();
150+
/// #
151+
/// # let door_uuri = UUri {
152+
/// # authority: Some(UAuthority {
153+
/// # name: Some("device_a".to_string()),
154+
/// # number: Some(Number::Ip(vec![192, 168, 1, 200])),
155+
/// # ..Default::default()
156+
/// # }).into(),
157+
/// # entity: Some(UEntity{
158+
/// # name: "body_access".to_string(),
159+
/// # id: Some(1),
160+
/// # version_major: Some(1),
161+
/// # ..Default::default()}).into(),
162+
/// # resource: Some(UResource {
163+
/// # name: "door".to_string(),
164+
/// # instance: Some("open_status".to_string()),
165+
/// # message: None,
166+
/// # id: Some(2),
167+
/// # ..Default::default()}).into(),
168+
/// # ..Default::default()};
169+
/// #
170+
/// # let my_listener = Arc::new(MyListener::new());
171+
/// #
172+
/// # let up_client = up_client_foo::UPClientFoo::new();
173+
/// #
174+
/// use std::sync::Arc;
175+
/// use up_rust::{UCode, UTransport,
176+
/// core::usubscription::{USubscription, UnsubscribeRequest, SubscribeAttributes,
177+
/// SubscriberInfo, SubscriptionRequest, SubscriptionResponse},
178+
/// };
179+
///
180+
/// let usub = usubscription_foo::USubscriptionFoo::new();
181+
///
182+
/// let subscriber_info = SubscriberInfo {
183+
/// uri: my_uuri,
184+
/// ..Default::default()
185+
/// };
186+
///
187+
/// let subscribe_attributes = SubscribeAttributes {
188+
/// sample_period_ms: Some(5000), // we want to hear about this every 5 seconds
189+
/// ..Default::default()
190+
/// };
191+
///
192+
/// let subscription_request = SubscriptionRequest {
193+
/// topic: Some(door_uuri.clone()).into(),
194+
/// subscriber: Some(subscriber_info.clone()).into(),
195+
/// attributes: Some(subscribe_attributes).into(),
196+
/// ..Default::default()
197+
/// };
198+
///
199+
/// let subscription_response = usub.subscribe(subscription_request).await?;
200+
/// let success_code = subscription_response.status.code.enum_value_or(UCode::UNKNOWN);
201+
/// if success_code == UCode::OK {
202+
/// let register_success = up_client.register_listener(door_uuri.clone(), my_listener.clone()).await;
203+
/// } else {
204+
/// match success_code {
205+
/// UCode::NOT_FOUND => { /* handle topic not found */ }
206+
/// _ => { /* handle other error conditions */ }
207+
/// }
208+
/// }
209+
/// // sometime later when done with this topic
210+
/// let unsubscribe_request = UnsubscribeRequest {
211+
/// topic: Some(door_uuri.clone()).into(),
212+
/// subscriber: Some(subscriber_info.clone()).into(),
213+
/// ..Default::default()
214+
/// };
215+
/// let unsubscribe_result = usub.unsubscribe(unsubscribe_request).await?;
216+
/// let unregister_success = up_client.register_listener(door_uuri.clone(), my_listener.clone()).await;
217+
/// #
218+
/// # Ok(())
219+
/// # }
220+
/// ```
221+
///
222+
/// For more information, please refer to the [uProtocol Specification](https://github.com/eclipse-uprotocol/up-spec/blob/main/up-l3/usubscription/v3/README.adoc)
223+
/// and [uProtocol APIs](https://github.com/eclipse-uprotocol/up-spec/blob/main/up-core-api/uprotocol/core/usubscription/v3/usubscription.proto)
224+
#[async_trait]
225+
pub trait USubscription: Send + Sync {
226+
/// Subscribe to a topic, using a [`SubscriptionRequest`]
227+
///
228+
/// # Parameters
229+
///
230+
/// * `subscription_request` - A request to subscribe
231+
///
232+
/// # Returns
233+
///
234+
/// * [`SubscriptionResponse`] detailing if subscription was successful with other metadata
235+
async fn subscribe(
236+
&self,
237+
subscription_request: SubscriptionRequest,
238+
) -> Result<SubscriptionResponse, UStatus>;
239+
240+
/// Unsubscribe to a topic, using an [`UnsubscribeRequest`]
241+
///
242+
/// # Parameters
243+
///
244+
/// * `unsubscribe_request` - A request to unsubscribe
245+
///
246+
/// # Returns
247+
///
248+
/// * [`UStatus`] detailing if unsubscription was successful and if not why not
249+
async fn unsubscribe(&self, unsubscribe_request: UnsubscribeRequest) -> Result<(), UStatus>;
250+
251+
/// Fetch all subscriptions for a given topic or subscriber contained inside a [`FetchSubscriptionsRequest`]
252+
///
253+
/// # Parameters
254+
///
255+
/// * `fetch_subscriptions_request` - A request to fetch subscriptions given a topic or subscriber
256+
///
257+
/// # Returns
258+
///
259+
/// * [`FetchSubscriptionsResponse`] detailing the zero or more subscriptions' info
260+
async fn fetch_subscriptions(
261+
&self,
262+
fetch_subscriptions_request: FetchSubscriptionsRequest,
263+
) -> Result<FetchSubscriptionsResponse, UStatus>;
264+
265+
/// Register for notifications relevant to a given topic inside a [`NotificationsRequest`]
266+
/// changing in subscription status.
267+
///
268+
/// # Parameters
269+
///
270+
/// * `notifications_register_request` - A request to receive changes to subscription status
271+
///
272+
/// # Returns
273+
///
274+
/// * [`UStatus`] detailing if notification registration was successful and if not why not
275+
async fn register_for_notifications(
276+
&self,
277+
notifications_register_request: NotificationsRequest,
278+
) -> Result<(), UStatus>;
279+
280+
/// Unregister for notifications relevant to a given topic inside a [`NotificationsRequest`]
281+
/// changing in subscription status.
282+
///
283+
/// # Parameters
284+
///
285+
/// * `notifications_unregister_request` - A request to no longer receive changes to subscription status
286+
///
287+
/// # Returns
288+
///
289+
/// * [`UStatus`] detailing if notification unregistration was successful and if not why not
290+
async fn unregister_for_notifications(
291+
&self,
292+
notifications_unregister_request: NotificationsRequest,
293+
) -> Result<(), UStatus>;
294+
295+
/// Fetch a list of subscribers that are currently subscribed to a given topic in a [`FetchSubscribersRequest`]
296+
///
297+
/// # Parameters
298+
///
299+
/// * `fetch_subscribers_request` - Request containing topic for which we'd like all subscribers' info
300+
///
301+
/// # Returns
302+
///
303+
/// * [`FetchSubscriptionsResponse`] detailing subscriber info for the provided topic
304+
async fn fetch_subscribers(
305+
&self,
306+
fetch_subscribers_request: FetchSubscribersRequest,
307+
) -> Result<FetchSubscriptionsResponse, UStatus>;
308+
}

src/core/utwin.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub use crate::up_core_api::utwin::{GetLastMessagesResponse, MessageResponse};

src/lib.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,7 @@ mod up_core_api {
8383
// pub use up_core_api::file;
8484
// pub use up_core_api::uprotocol_options;
8585

86-
// Types not used by up_rust, but re-exported to up_rust users, keeping them in their respective submodules
87-
#[cfg(feature = "udiscovery")]
88-
pub use up_core_api::udiscovery;
89-
#[cfg(feature = "usubscription")]
90-
pub use up_core_api::usubscription;
91-
#[cfg(feature = "utwin")]
92-
pub use up_core_api::utwin;
86+
pub mod core;
9387

9488
// cloudevent-proto, generated and augmented types
9589
#[cfg(feature = "cloudevents")]

0 commit comments

Comments
 (0)