reproto_core/
rp_endpoint.rs

1//! Model for endpoints
2
3use errors::Result;
4use std::default;
5use std::rc::Rc;
6use {Attributes, Diagnostics, Flavor, Loc, RpChannel, RpPathSpec, Translate, Translator};
7
8#[derive(Debug, Clone, Serialize)]
9pub enum RpHttpMethod {
10    Get,
11    Post,
12    Put,
13    Update,
14    Delete,
15    Patch,
16    Head,
17}
18
19impl RpHttpMethod {
20    /// Treat this method to an all uppercase string representing the method.
21    pub fn as_str(&self) -> &str {
22        use self::RpHttpMethod::*;
23
24        match *self {
25            Get => "GET",
26            Post => "POST",
27            Put => "PUT",
28            Update => "UPDATE",
29            Delete => "DELETE",
30            Patch => "PATCH",
31            Head => "HEAD",
32        }
33    }
34}
35
36#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
37pub enum RpAccept {
38    #[serde(rename = "json")]
39    Json,
40    #[serde(rename = "text")]
41    Text,
42}
43
44impl default::Default for RpAccept {
45    fn default() -> Self {
46        RpAccept::Json
47    }
48}
49
50#[derive(Debug, Clone, Serialize, Default)]
51#[serde(bound = "F: ::serde::Serialize, F::Type: ::serde::Serialize")]
52pub struct RpEndpointHttp<F: 'static>
53where
54    F: Flavor,
55{
56    /// Path specification.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub path: Option<RpPathSpec<F>>,
59    /// Argument that is the body of the request.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub body: Option<RpEndpointArgument<F>>,
62    /// HTTP method.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub method: Option<RpHttpMethod>,
65    /// Accepted media types.
66    pub accept: RpAccept,
67}
68
69impl<F: 'static, T> Translate<T> for RpEndpointHttp<F>
70where
71    F: Flavor,
72    T: Translator<Source = F>,
73{
74    type Source = F;
75    type Out = RpEndpointHttp<T::Target>;
76
77    /// Translate into different flavor.
78    fn translate(
79        self,
80        diag: &mut Diagnostics,
81        translator: &T,
82    ) -> Result<RpEndpointHttp<T::Target>> {
83        Ok(RpEndpointHttp {
84            path: self.path.translate(diag, translator)?,
85            body: self.body.translate(diag, translator)?,
86            method: self.method,
87            accept: self.accept,
88        })
89    }
90}
91
92/// An argument to an endpont.
93#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
94#[serde(bound = "F::Type: ::serde::Serialize")]
95pub struct RpEndpointArgument<F: 'static>
96where
97    F: Flavor,
98{
99    /// Identifier of the argument.
100    pub ident: Rc<Loc<String>>,
101    /// Safe identifier for the argument.
102    pub safe_ident: Rc<Option<String>>,
103    /// Channel of the argument.
104    pub channel: Loc<RpChannel<F>>,
105}
106
107impl<F: 'static, T> Translate<T> for RpEndpointArgument<F>
108where
109    F: Flavor,
110    T: Translator<Source = F>,
111{
112    type Source = F;
113    type Out = RpEndpointArgument<T::Target>;
114
115    /// Translate into different flavor.
116    fn translate(
117        self,
118        diag: &mut Diagnostics,
119        translator: &T,
120    ) -> Result<RpEndpointArgument<T::Target>> {
121        Ok(RpEndpointArgument {
122            ident: self.ident,
123            safe_ident: self.safe_ident,
124            channel: self.channel.translate(diag, translator)?,
125        })
126    }
127}
128
129impl<F: 'static> RpEndpointArgument<F>
130where
131    F: Flavor,
132{
133    /// Access the actual identifier of the endpoint argument.
134    pub fn ident(&self) -> &str {
135        self.ident.as_str()
136    }
137
138    /// Access the safe identifier for the endpoint argument.
139    pub fn safe_ident(&self) -> &str {
140        Option::as_ref(&self.safe_ident)
141            .map(|s| s.as_str())
142            .unwrap_or_else(|| self.ident.as_str())
143    }
144}
145
146#[derive(Debug, Clone, Serialize)]
147#[serde(bound = "F: ::serde::Serialize, F::Type: ::serde::Serialize")]
148pub struct RpEndpoint<F: 'static>
149where
150    F: Flavor,
151{
152    /// Name of the endpoint. Guaranteed to be unique.
153    pub ident: String,
154    /// Safe identifier of the endpoint, avoiding any language-specific keywords.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub safe_ident: Option<String>,
157    /// Name of the endpoint. This is the name which is being sent over the wire.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub name: Option<String>,
160    /// Comments for documentation.
161    pub comment: Vec<String>,
162    /// Attributes associated with the endpoint.
163    pub attributes: Attributes,
164    /// Request type that this endpoint expects.
165    pub arguments: Vec<RpEndpointArgument<F>>,
166    /// Request type that this endpoint accepts with.
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub request: Option<RpEndpointArgument<F>>,
169    /// Response type that this endpoint responds with.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub response: Option<Loc<RpChannel<F>>>,
172    /// HTTP configuration.
173    pub http: RpEndpointHttp<F>,
174}
175
176impl<F: 'static> RpEndpoint<F>
177where
178    F: Flavor,
179{
180    pub fn id_parts<T>(&self, filter: T) -> Vec<String>
181    where
182        T: Fn(&str) -> String,
183    {
184        vec![filter(self.ident.as_str())]
185    }
186
187    /// Get the name of the endpoint.
188    pub fn name(&self) -> &str {
189        self.name
190            .as_ref()
191            .map(|s| s.as_str())
192            .unwrap_or(self.ident())
193    }
194
195    /// Safe identifier of the endpoint.
196    pub fn safe_ident(&self) -> &str {
197        self.safe_ident
198            .as_ref()
199            .map(|s| s.as_str())
200            .unwrap_or(self.ident.as_str())
201    }
202
203    /// Get the identifier of the endpoint.
204    pub fn ident(&self) -> &str {
205        self.ident.as_str()
206    }
207
208    /// If endpoint has metadata for HTTP.
209    pub fn has_http_support(&self) -> bool {
210        if !self.http.path.is_some() {
211            return false;
212        }
213
214        true
215    }
216}
217
218impl<F: 'static, T> Translate<T> for RpEndpoint<F>
219where
220    F: Flavor,
221    T: Translator<Source = F>,
222{
223    type Source = F;
224    type Out = RpEndpoint<T::Target>;
225
226    /// Translate into different flavor.
227    fn translate(self, diag: &mut Diagnostics, translator: &T) -> Result<RpEndpoint<T::Target>> {
228        Ok(RpEndpoint {
229            ident: self.ident,
230            safe_ident: self.safe_ident,
231            name: self.name,
232            comment: self.comment,
233            attributes: self.attributes,
234            arguments: self.arguments.translate(diag, translator)?,
235            request: self.request.translate(diag, translator)?,
236            response: self.response.translate(diag, translator)?,
237            http: self.http.translate(diag, translator)?,
238        })
239    }
240}
241
242/// A model that describes the endpoint as an HTTP/1.1 endpoint.
243#[derive(Debug, Clone)]
244pub struct RpEndpointHttp1<F: 'static>
245where
246    F: Flavor,
247{
248    pub request: Option<F::Type>,
249    pub response: Option<F::Type>,
250    pub path: RpPathSpec<F>,
251    pub method: RpHttpMethod,
252}
253
254impl<F: 'static> RpEndpointHttp1<F>
255where
256    F: Clone + Flavor,
257{
258    /// Convert the general HTTP information into HTTP/1.1 if applicable.
259    pub fn from_endpoint(endpoint: &RpEndpoint<F>) -> Option<RpEndpointHttp1<F>> {
260        use self::RpChannel::*;
261
262        // HTTP/1.1 requires a path.
263        let path = match endpoint.http.path.as_ref() {
264            Some(path) => path.clone(),
265            None => return None,
266        };
267
268        let request_ty = endpoint.request.as_ref().map(|r| Loc::borrow(&r.channel));
269        let response_ty = endpoint.response.as_ref().map(|r| Loc::borrow(r));
270
271        let (request, response) = match (request_ty, response_ty) {
272            (Some(&Unary { ty: ref request }), Some(&Unary { ty: ref response })) => {
273                (Some(request.clone()), Some(response.clone()))
274            }
275            (None, Some(&Unary { ty: ref response })) => (None, Some(response.clone())),
276            (Some(&Unary { ty: ref request }), None) => (Some(request.clone()), None),
277            (None, None) => (None, None),
278            _ => return None,
279        };
280
281        let method = endpoint
282            .http
283            .method
284            .as_ref()
285            .cloned()
286            .unwrap_or(RpHttpMethod::Get);
287
288        return Some(RpEndpointHttp1 {
289            request: request,
290            response: response,
291            path: path,
292            method: method,
293        });
294    }
295}