yeti_types/resource/
entry.rs1use std::sync::Arc;
16
17use super::metadata::ResourceMetadata;
18
19#[derive(Clone)]
23pub struct ResourceEntry {
24 resource: Arc<dyn ResourceMetadata>,
25}
26
27impl std::fmt::Debug for ResourceEntry {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("ResourceEntry")
30 .field("name", &self.resource.name())
31 .finish_non_exhaustive()
32 }
33}
34
35impl ResourceEntry {
36 #[must_use]
39 pub fn new<R>(resource: R) -> Self
40 where
41 R: ResourceMetadata + 'static,
42 {
43 Self {
44 resource: Arc::new(resource),
45 }
46 }
47
48 #[must_use]
51 pub fn from_arc(resource: Arc<dyn ResourceMetadata>) -> Self {
52 Self { resource }
53 }
54
55 #[must_use]
57 pub fn metadata(&self) -> &(dyn ResourceMetadata + 'static) {
58 &*self.resource
59 }
60
61 #[must_use]
64 pub fn resource(&self) -> Arc<dyn ResourceMetadata> {
65 Arc::clone(&self.resource)
66 }
67
68 #[must_use]
70 pub fn name(&self) -> &str {
71 self.resource.name()
72 }
73}
74
75#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::resource::context::Context;
83 use crate::resource::{ResourceFuture, ResponseBody};
84 use bytes::Bytes;
85 use http::{HeaderMap, Method, Response};
86
87 struct EchoVerb;
90
91 impl ResourceMetadata for EchoVerb {
92 fn name(&self) -> &'static str {
93 "echo"
94 }
95
96 fn handle(&self, ctx: Context) -> ResourceFuture {
97 let method = ctx.method().clone();
98 Box::pin(async move {
99 #[allow(clippy::expect_used)]
100 let resp = match method {
101 Method::GET => Response::builder()
102 .status(200)
103 .body(ResponseBody::complete(b"GET".to_vec()))
104 .expect("static response"),
105 Method::POST => Response::builder()
106 .status(200)
107 .body(ResponseBody::complete(b"POST".to_vec()))
108 .expect("static response"),
109 _ => Response::builder()
110 .status(405)
111 .body(ResponseBody::complete(b"Method Not Allowed".to_vec()))
112 .expect("static response"),
113 };
114 Ok(resp)
115 })
116 }
117 }
118
119 fn ctx_for(method: Method) -> Context {
120 Context::from_request(method, "/echo".to_owned(), Bytes::new(), HeaderMap::new())
121 }
122
123 #[tokio::test]
124 async fn entry_dispatches_get_through_handle() {
125 let entry = ResourceEntry::new(EchoVerb);
126 assert_eq!(entry.name(), "echo");
127 let resp = entry
128 .resource()
129 .handle(ctx_for(Method::GET))
130 .await
131 .expect("call");
132 assert_eq!(resp.status(), 200);
133 assert_eq!(resp.body().as_bytes(), Some(&b"GET"[..]));
134 }
135
136 #[tokio::test]
137 async fn entry_metadata_resolves_name() {
138 let entry = ResourceEntry::new(EchoVerb);
139 assert_eq!(entry.metadata().name(), "echo");
140 }
141
142 #[tokio::test]
143 async fn entry_unknown_verb_returns_405() {
144 let entry = ResourceEntry::new(EchoVerb);
145 let resp = entry
146 .resource()
147 .handle(ctx_for(Method::DELETE))
148 .await
149 .expect("call");
150 assert_eq!(resp.status(), 405);
151 }
152
153 #[tokio::test]
156 async fn metadata_only_resource_defaults_to_405() {
157 struct PermsOnly;
158 impl ResourceMetadata for PermsOnly {
159 fn name(&self) -> &'static str {
160 "perms"
161 }
162 }
163 let entry = ResourceEntry::new(PermsOnly);
164 let resp = entry
165 .resource()
166 .handle(ctx_for(Method::GET))
167 .await
168 .expect("call");
169 assert_eq!(resp.status(), 405);
170 }
171}