azure_functions_durable/
endpoint.rs

1use std::fmt::Write;
2use url::Url;
3
4/// Represents a Durable Functions HTTP API endpoint.
5#[derive(Debug, Clone)]
6pub struct Endpoint {
7    base_uri: Url,
8    task_hub: String,
9    connection: String,
10    code: String,
11}
12
13impl Endpoint {
14    /// Create a new endpoint from a status query URL.
15    pub fn new(status_query_url: Url) -> Self {
16        let mut task_hub = None;
17        let mut connection = None;
18        let mut code = None;
19
20        for (k, v) in status_query_url.query_pairs() {
21            match k.to_ascii_lowercase().as_ref() {
22                "taskhub" => task_hub = Some(v.into_owned()),
23                "connection" => connection = Some(v.into_owned()),
24                "code" => code = Some(v.into_owned()),
25                _ => {}
26            };
27        }
28
29        Self {
30            base_uri: status_query_url,
31            task_hub: task_hub.expect("expected a taskhub parameter"),
32            connection: connection.expect("expected a connection parameter"),
33            code: code.expect("expected a code parameter"),
34        }
35    }
36
37    /// Gets the task hub associated with the endpoint.
38    pub fn task_hub(&self) -> &str {
39        &self.task_hub
40    }
41
42    /// Gets the "create new instance" URL from the endpoint.
43    pub fn create_new_instance_url(&self, function_name: &str, instance_id: Option<&str>) -> Url {
44        let mut url = self.base_uri.clone();
45
46        let path = match instance_id {
47            Some(id) => format!(
48                "/runtime/webhooks/durabletask/orchestrators/{}/{}",
49                function_name, id
50            ),
51            None => format!(
52                "/runtime/webhooks/durabletask/orchestrators/{}",
53                function_name
54            ),
55        };
56
57        url.set_path(&path);
58
59        url.query_pairs_mut()
60            .clear()
61            .append_pair("code", &self.code);
62
63        url
64    }
65
66    /// Gets the "status query" URL.
67    pub fn status_query_url(&self, instance_id: Option<&str>) -> Url {
68        self.build_query_url(instance_id, None)
69    }
70
71    /// Gets the "purge history" URL.
72    pub fn purge_history_url(&self, instance_id: Option<&str>) -> Url {
73        self.build_query_url(instance_id, None)
74    }
75
76    /// Gets the "rewind history" URL.
77    pub fn rewind_url(&self, instance_id: &str, reason: &str) -> Url {
78        let mut url = self.build_query_url(Some(instance_id), Some("rewind"));
79        url.query_pairs_mut().append_pair("reason", reason);
80        url
81    }
82
83    /// Gets the "raise event" URL.
84    pub fn raise_event_url(&self, instance_id: &str, event_name: &str) -> Url {
85        self.build_query_url(
86            Some(instance_id),
87            Some(&format!("raiseEvent/{}", event_name)),
88        )
89    }
90
91    /// Gets the "terminate instance" URL.
92    pub fn terminate_url(&self, instance_id: &str, reason: &str) -> Url {
93        let mut url = self.build_query_url(Some(instance_id), Some("terminate"));
94        url.query_pairs_mut().append_pair("reason", reason);
95        url
96    }
97
98    fn build_query_url(&self, instance_id: Option<&str>, action: Option<&str>) -> Url {
99        let mut url = self.base_uri.clone();
100        let mut path = "/runtime/webhooks/durabletask/instances".to_string();
101
102        if let Some(id) = instance_id {
103            write!(&mut path, "/{}", id).unwrap();
104        }
105
106        if let Some(action) = action {
107            write!(&mut path, "/{}", action).unwrap();
108        }
109
110        url.set_path(&path);
111
112        url.query_pairs_mut()
113            .clear()
114            .append_pair("taskHub", &self.task_hub)
115            .append_pair("connection", &self.connection)
116            .append_pair("code", &self.code);
117
118        url
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_endpoint_parsing() {
128        let endpoint = Endpoint::new(Url::parse("http://localhost:7071/runtime/webhooks/durabletask/instances/INSTANCEID?taskHub=myHub&connection=Storage&code=myCode").unwrap());
129        assert_eq!(endpoint.code, "myCode");
130
131        let rewind_result = "http://localhost:7071/runtime/webhooks/durabletask/instances/1234/rewind?taskHub=myHub&connection=Storage&code=myCode&reason=myReason";
132        let rewind_url = endpoint.rewind_url("1234", "myReason");
133        assert_eq!(rewind_url.to_string(), rewind_result);
134    }
135
136    #[test]
137    #[should_panic]
138    fn test_bad_endpoint() {
139        Endpoint::new(
140            Url::parse("http://localhost:7071/runtime/webhooks/durabletask/instances/INSTANC")
141                .unwrap(),
142        );
143    }
144}