Skip to main content

tide_disco/
api.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the tide-disco library.
3
4// You should have received a copy of the MIT License
5// along with the tide-disco library. If not, see <https://mit-license.org/>.
6
7use crate::{
8    Html,
9    healthcheck::{HealthCheck, HealthStatus},
10    method::{Method, ReadState, WriteState},
11    metrics::Metrics,
12    middleware::{ErrorHandler, error_handler},
13    request::RequestParams,
14    route::{self, *},
15    socket,
16};
17use async_std::sync::Arc;
18use async_trait::async_trait;
19use derivative::Derivative;
20use futures::{
21    future::{BoxFuture, FutureExt},
22    stream::BoxStream,
23};
24use maud::{PreEscaped, html};
25use semver::Version;
26use serde::{Deserialize, Serialize, de::DeserializeOwned};
27use serde_with::{DisplayFromStr, serde_as};
28use snafu::{OptionExt, ResultExt, Snafu};
29use std::{
30    borrow::Cow,
31    collections::hash_map::{Entry, HashMap, IntoValues, Values},
32    convert::Infallible,
33    fmt::Display,
34    fs,
35    marker::PhantomData,
36    ops::Index,
37    path::{Path, PathBuf},
38};
39use tide::http::content::Accept;
40use vbs::version::StaticVersionType;
41
42/// An error encountered when parsing or constructing an [Api].
43#[derive(Clone, Debug, Snafu, PartialEq, Eq)]
44pub enum ApiError {
45    Route { source: RouteParseError },
46    ApiMustBeTable,
47    MissingRoutesTable,
48    RoutesMustBeTable,
49    UndefinedRoute,
50    HandlerAlreadyRegistered,
51    IncorrectMethod { expected: Method, actual: Method },
52    InvalidMetaTable { source: toml::de::Error },
53    MissingFormatVersion,
54    InvalidFormatVersion,
55    AmbiguousRoutes { route1: String, route2: String },
56    CannotReadToml { reason: String },
57}
58
59/// Version information about an API.
60#[serde_as]
61#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
62pub struct ApiVersion {
63    /// The version of this API.
64    #[serde_as(as = "Option<DisplayFromStr>")]
65    pub api_version: Option<Version>,
66
67    /// The format version of the TOML specification used to load this API.
68    #[serde_as(as = "DisplayFromStr")]
69    pub spec_version: Version,
70}
71
72/// Metadata used for describing and documenting an API.
73///
74/// [ApiMetadata] contains version information about the API, as well as optional HTML fragments to
75/// customize the formatting of automatically generated API documentation. Each of the supported
76/// HTML fragments is optional and will be filled in with a reasonable default if not provided. Some
77/// of the HTML fragments may contain "placeholders", which are identifiers enclosed in `{{ }}`,
78/// like `{{SOME_PLACEHOLDER}}`. These will be replaced by contextual information when the
79/// documentation is generated. The placeholders supported by each HTML fragment are documented
80/// below.
81#[serde_as]
82#[derive(Clone, Debug, Deserialize, Serialize)]
83#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
84pub struct ApiMetadata {
85    /// The name of this API.
86    ///
87    /// Note that the name of the API may be overridden if the API is registered with an app using
88    /// a different name.
89    #[serde(default = "meta_defaults::name")]
90    pub name: String,
91
92    /// A description of this API.
93    #[serde(default = "meta_defaults::description")]
94    pub description: String,
95
96    /// The version of the Tide Disco API specification format.
97    ///
98    /// If not specified, the version of this crate will be used.
99    #[serde_as(as = "DisplayFromStr")]
100    #[serde(default = "meta_defaults::format_version")]
101    pub format_version: Version,
102
103    /// HTML to be prepended to automatically generated documentation.
104    ///
105    /// # Placeholders
106    ///
107    /// * `NAME`: the name of the API
108    /// * `DESCRIPTION`: the description provided in `Cargo.toml`
109    /// * `VERSION`: the version of the API
110    /// * `FORMAT_VERSION`: the `FORMAT_VERSION` of the API
111    /// * `PUBLIC`: the URL where the public directory for this API is being served
112    #[serde(default = "meta_defaults::html_top")]
113    pub html_top: String,
114
115    /// HTML to be appended to automatically generated documentation.
116    #[serde(default = "meta_defaults::html_bottom")]
117    pub html_bottom: String,
118
119    /// The heading for documentation of a route.
120    ///
121    /// # Placeholders
122    ///
123    /// * `METHOD`: the method of the route
124    /// * `NAME`: the name of the route
125    #[serde(default = "meta_defaults::heading_entry")]
126    pub heading_entry: String,
127
128    /// The heading preceding documentation of all routes in this API.
129    #[serde(default = "meta_defaults::heading_routes")]
130    pub heading_routes: String,
131
132    /// The heading preceding documentation of route parameters.
133    #[serde(default = "meta_defaults::heading_parameters")]
134    pub heading_parameters: String,
135
136    /// The heading preceding documentation of a route description.
137    #[serde(default = "meta_defaults::heading_description")]
138    pub heading_description: String,
139
140    /// HTML formatting the path of a route.
141    ///
142    /// # Placeholders
143    ///
144    /// * `PATH`: the path being formatted
145    #[serde(default = "meta_defaults::route_path")]
146    pub route_path: String,
147
148    /// HTML preceding the contents of a table documenting the parameters of a route.
149    #[serde(default = "meta_defaults::parameter_table_open")]
150    pub parameter_table_open: String,
151
152    /// HTML closing a table documenting the parameters of a route.
153    #[serde(default = "meta_defaults::parameter_table_close")]
154    pub parameter_table_close: String,
155
156    /// HTML formatting an entry in a table documenting the parameters of a route.
157    ///
158    /// # Placeholders
159    ///
160    /// * `NAME`: the parameter being documented
161    /// * `TYPE`: the type of the parameter being documented
162    #[serde(default = "meta_defaults::parameter_row")]
163    pub parameter_row: String,
164
165    /// Documentation to insert in the parameters section of a route with no parameters.
166    #[serde(default = "meta_defaults::parameter_none")]
167    pub parameter_none: String,
168}
169
170impl Default for ApiMetadata {
171    fn default() -> Self {
172        // Deserialize an empty table, using the `serde` defaults for every field.
173        toml::Value::Table(Default::default()).try_into().unwrap()
174    }
175}
176
177mod meta_defaults {
178    use super::Version;
179
180    pub fn name() -> String {
181        "default-tide-disco-api".to_string()
182    }
183
184    pub fn description() -> String {
185        "Default Tide Disco API".to_string()
186    }
187
188    pub fn format_version() -> Version {
189        "0.1.0".parse().unwrap()
190    }
191
192    pub fn html_top() -> String {
193        "
194        <!DOCTYPE html>
195        <html lang='en'>
196          <head>
197            <meta charset='utf-8'>
198            <title>{{NAME}} Reference</title>
199            <link rel='stylesheet' href='{{PUBLIC}}/css/style.css'>
200            <script src='{{PUBLIC}}/js/script.js'></script>
201            <link rel='icon' type='image/svg+xml'
202             href='/public/favicon.svg'>
203          </head>
204          <body>
205            <div><a href='/'><img src='{{PUBLIC}}/espressosys_logo.svg'
206                      alt='Espresso Systems Logo'
207                      /></a></div>
208            <h1>{{NAME}} API {{VERSION}} Reference</h1>
209            <p>{{SHORT_DESCRIPTION}}</p><br/>
210            {{LONG_DESCRIPTION}}
211        "
212        .to_string()
213    }
214
215    pub fn html_bottom() -> String {
216        "
217            <h1>&nbsp;</h1>
218            <p>Copyright © 2022 Espresso Systems. All rights reserved.</p>
219          </body>
220        </html>
221        "
222        .to_string()
223    }
224
225    pub fn heading_entry() -> String {
226        "<a name='{{NAME}}'><h3 class='entry'><span class='meth'>{{METHOD}}</span> {{NAME}}</h3></a>\n".to_string()
227    }
228
229    pub fn heading_routes() -> String {
230        "<h3>Routes</h3>\n".to_string()
231    }
232    pub fn heading_parameters() -> String {
233        "<h3>Parameters</h3>\n".to_string()
234    }
235    pub fn heading_description() -> String {
236        "<h3>Description</h3>\n".to_string()
237    }
238
239    pub fn route_path() -> String {
240        "<p class='path'>{{PATH}}</p>\n".to_string()
241    }
242
243    pub fn parameter_table_open() -> String {
244        "<table>\n".to_string()
245    }
246    pub fn parameter_table_close() -> String {
247        "</table>\n\n".to_string()
248    }
249    pub fn parameter_row() -> String {
250        "<tr><td class='parameter'>{{NAME}}</td><td class='type'>{{TYPE}}</td></tr>\n".to_string()
251    }
252    pub fn parameter_none() -> String {
253        "<div class='meta'>None</div>".to_string()
254    }
255}
256
257/// A description of an API.
258///
259/// An [Api] is a structured representation of an `api.toml` specification. It contains API-level
260/// metadata and descriptions of all of the routes in the specification. It can be parsed from a
261/// TOML file and registered as a module of an [App](crate::App).
262#[derive(Derivative)]
263#[derivative(Debug(bound = ""))]
264pub struct Api<State, Error, VER> {
265    inner: ApiInner<State, Error>,
266    _version: PhantomData<VER>,
267}
268
269/// A version-erased description of an API.
270///
271/// This type contains all the details of the API, with the version of the binary serialization
272/// format type-erased and encapsulated into the route handlers. This type is used internally by
273/// [`App`], to allow dynamic registration of different versions of APIs with different versions of
274/// the binary format.
275///
276/// It is exposed publicly and manipulated _only_ via [`Api`], which wraps this type with a static
277/// format version type parameter, which provides compile-time enforcement of format version
278/// consistency as the API is being constructed, until it is registered with an [`App`] and
279/// type-erased.
280#[derive(Derivative)]
281#[derivative(Debug(bound = ""))]
282pub(crate) struct ApiInner<State, Error> {
283    meta: Arc<ApiMetadata>,
284    name: String,
285    routes: HashMap<String, Route<State, Error>>,
286    routes_by_path: HashMap<String, Vec<String>>,
287    #[derivative(Debug = "ignore")]
288    health_check: HealthCheckHandler<State>,
289    api_version: Option<Version>,
290    /// Error handler encapsulating the serialization format version for errors.
291    ///
292    /// This field is optional so it can be bound late, potentially after a `map_err` changes the
293    /// error type. However, it will always be set after `Api::into_inner` is called.
294    #[derivative(Debug = "ignore")]
295    error_handler: Option<Arc<dyn ErrorHandler<Error>>>,
296    /// Response handler encapsulating the serialization format version for version requests
297    #[derivative(Debug = "ignore")]
298    version_handler: Arc<dyn VersionHandler>,
299    public: Option<PathBuf>,
300    short_description: String,
301    long_description: String,
302}
303
304pub(crate) trait VersionHandler:
305    Send + Sync + Fn(&Accept, ApiVersion) -> Result<tide::Response, RouteError<Infallible>>
306{
307}
308impl<F> VersionHandler for F where
309    F: Send + Sync + Fn(&Accept, ApiVersion) -> Result<tide::Response, RouteError<Infallible>>
310{
311}
312
313impl<'a, State, Error> IntoIterator for &'a ApiInner<State, Error> {
314    type Item = &'a Route<State, Error>;
315    type IntoIter = Values<'a, String, Route<State, Error>>;
316
317    fn into_iter(self) -> Self::IntoIter {
318        self.routes.values()
319    }
320}
321
322impl<State, Error> IntoIterator for ApiInner<State, Error> {
323    type Item = Route<State, Error>;
324    type IntoIter = IntoValues<String, Route<State, Error>>;
325
326    fn into_iter(self) -> Self::IntoIter {
327        self.routes.into_values()
328    }
329}
330
331impl<State, Error> Index<&str> for ApiInner<State, Error> {
332    type Output = Route<State, Error>;
333
334    fn index(&self, index: &str) -> &Route<State, Error> {
335        &self.routes[index]
336    }
337}
338
339/// Iterator for [routes_by_path](ApiInner::routes_by_path).
340///
341/// This type iterates over all of the routes that have a given path.
342/// [routes_by_path](ApiInner::routes_by_path), in turn, returns an iterator over paths whose items
343/// contain a [RoutesWithPath] iterator.
344pub(crate) struct RoutesWithPath<'a, State, Error> {
345    routes: std::slice::Iter<'a, String>,
346    api: &'a ApiInner<State, Error>,
347}
348
349impl<'a, State, Error> Iterator for RoutesWithPath<'a, State, Error> {
350    type Item = &'a Route<State, Error>;
351
352    fn next(&mut self) -> Option<Self::Item> {
353        Some(&self.api.routes[self.routes.next()?])
354    }
355}
356
357impl<State, Error> ApiInner<State, Error> {
358    /// Iterate over groups of routes with the same path.
359    pub(crate) fn routes_by_path(
360        &self,
361    ) -> impl Iterator<Item = (&str, RoutesWithPath<'_, State, Error>)> {
362        self.routes_by_path.iter().map(|(path, routes)| {
363            (
364                path.as_str(),
365                RoutesWithPath {
366                    routes: routes.iter(),
367                    api: self,
368                },
369            )
370        })
371    }
372
373    /// Check the health status of a server with the given state.
374    pub(crate) async fn health(&self, req: RequestParams, state: &State) -> tide::Response {
375        (self.health_check)(req, state).await
376    }
377
378    /// Get the version of this API.
379    pub(crate) fn version(&self) -> ApiVersion {
380        ApiVersion {
381            api_version: self.api_version.clone(),
382            spec_version: self.meta.format_version.clone(),
383        }
384    }
385
386    pub(crate) fn public(&self) -> Option<&PathBuf> {
387        self.public.as_ref()
388    }
389
390    pub(crate) fn set_name(&mut self, name: String) {
391        self.name = name;
392    }
393
394    /// Compose an HTML page documenting all the routes in this API.
395    pub(crate) fn documentation(&self) -> Html {
396        html! {
397            (PreEscaped(self.meta.html_top
398                .replace("{{NAME}}", &self.name)
399                .replace("{{SHORT_DESCRIPTION}}", &self.short_description)
400                .replace("{{LONG_DESCRIPTION}}", &self.long_description)
401                .replace("{{VERSION}}", &match &self.api_version {
402                    Some(version) => version.to_string(),
403                    None => "(no version)".to_string(),
404                })
405                .replace("{{FORMAT_VERSION}}", &self.meta.format_version.to_string())
406                .replace("{{PUBLIC}}", &format!("/public/{}", self.name))))
407            @for route in self.routes.values() {
408                (route.documentation())
409            }
410            (PreEscaped(&self.meta.html_bottom))
411        }
412    }
413
414    /// The short description of this API from the specification.
415    pub(crate) fn short_description(&self) -> &str {
416        &self.short_description
417    }
418
419    pub(crate) fn error_handler(&self) -> Arc<dyn ErrorHandler<Error>> {
420        self.error_handler.clone().unwrap()
421    }
422
423    pub(crate) fn version_handler(&self) -> Arc<dyn VersionHandler> {
424        self.version_handler.clone()
425    }
426}
427
428impl<State, Error, VER> Api<State, Error, VER>
429where
430    State: 'static,
431    Error: 'static,
432    VER: StaticVersionType + 'static,
433{
434    /// Parse an API from a TOML specification.
435    pub fn new(api: impl Into<toml::Value>) -> Result<Self, ApiError> {
436        let mut api = api.into();
437        let meta = match api
438            .as_table_mut()
439            .context(ApiMustBeTableSnafu)?
440            .remove("meta")
441        {
442            Some(meta) => toml::Value::try_into(meta)
443                .map_err(|source| ApiError::InvalidMetaTable { source })?,
444            None => ApiMetadata::default(),
445        };
446        let meta = Arc::new(meta);
447        let routes = match api.get("route") {
448            Some(routes) => routes.as_table().context(RoutesMustBeTableSnafu)?,
449            None => return Err(ApiError::MissingRoutesTable),
450        };
451        // Collect routes into a [HashMap] indexed by route name.
452        let routes = routes
453            .into_iter()
454            .map(|(name, spec)| {
455                let route = Route::new(name.clone(), spec, meta.clone()).context(RouteSnafu)?;
456                Ok((route.name(), route))
457            })
458            .collect::<Result<HashMap<_, _>, _>>()?;
459        // Collect routes into groups of route names indexed by route pattern.
460        let mut routes_by_path = HashMap::new();
461        for route in routes.values() {
462            for path in route.patterns() {
463                match routes_by_path.entry(path.clone()) {
464                    Entry::Vacant(e) => e.insert(Vec::new()).push(route.name().clone()),
465                    Entry::Occupied(mut e) => {
466                        // If there is already a route with this path and method, then dispatch is
467                        // ambiguous.
468                        if let Some(ambiguous_name) = e
469                            .get()
470                            .iter()
471                            .find(|name| routes[*name].method() == route.method())
472                        {
473                            return Err(ApiError::AmbiguousRoutes {
474                                route1: route.name(),
475                                route2: ambiguous_name.clone(),
476                            });
477                        }
478                        e.get_mut().push(route.name());
479                    }
480                }
481            }
482        }
483
484        // Parse description: the first line is a short description, to display when briefly
485        // describing this API in a list. The rest is the long description, to display on this API's
486        // own documentation page. Both are rendered to HTML via Markdown.
487        let blocks = markdown::tokenize(&meta.description);
488        let (short_description, long_description) = match blocks.split_first() {
489            Some((short, long)) => {
490                let render = |blocks| markdown::to_html(&markdown::generate_markdown(blocks));
491
492                let short = render(vec![short.clone()]);
493                let long = render(long.to_vec());
494
495                // The short description is only one block, and sometimes we would like to display
496                // it inline (as a `span`). Markdown automatically wraps blocks in `<p>`. We will
497                // strip this outer tag so that we can wrap it in either `<p>` or `<span>`,
498                // depending on the context.
499                let short = short.strip_prefix("<p>").unwrap_or(&short);
500                let short = short.strip_suffix("</p>").unwrap_or(short);
501                let short = short.to_string();
502
503                (short, long)
504            }
505            None => Default::default(),
506        };
507
508        Ok(Self {
509            inner: ApiInner {
510                name: meta.name.clone(),
511                meta,
512                routes,
513                routes_by_path,
514                health_check: Box::new(Self::default_health_check),
515                api_version: None,
516                error_handler: None,
517                version_handler: Arc::new(|accept, version| {
518                    respond_with(accept, version, VER::instance())
519                }),
520                public: None,
521                short_description,
522                long_description,
523            },
524            _version: Default::default(),
525        })
526    }
527
528    /// Create an [Api] by reading a TOML specification from a file.
529    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ApiError> {
530        let bytes = fs::read(path).map_err(|err| ApiError::CannotReadToml {
531            reason: err.to_string(),
532        })?;
533        let string = std::str::from_utf8(&bytes).map_err(|err| ApiError::CannotReadToml {
534            reason: err.to_string(),
535        })?;
536        Self::new(toml::from_str::<toml::Value>(string).map_err(|err| {
537            ApiError::CannotReadToml {
538                reason: err.to_string(),
539            }
540        })?)
541    }
542
543    /// Set the API version.
544    ///
545    /// The version information will automatically be included in responses to `GET /version`. This
546    /// version can also be used to serve multiple major versions of the same API simultaneously,
547    /// under a version prefix. For more information, see
548    /// [App::register_module](crate::App::register_module).
549    ///
550    /// This is the version of the application or sub-application which this instance of [Api]
551    /// represents. The versioning corresponds to the API specification passed to [new](Api::new),
552    /// and may be different from the version of the Rust crate implementing the route handlers for
553    /// the API.
554    pub fn with_version(&mut self, version: Version) -> &mut Self {
555        self.inner.api_version = Some(version);
556        self
557    }
558
559    /// Serve the contents of `dir` at the URL `/public/{{NAME}}`.
560    pub fn with_public(&mut self, dir: PathBuf) -> &mut Self {
561        self.inner.public = Some(dir);
562        self
563    }
564
565    /// Register a handler for a route.
566    ///
567    /// When the server receives a request whose URL matches the pattern of the route `name`,
568    /// `handler` will be invoked with the parameters of the request and a reference to the current
569    /// state, and the result will be serialized into a response.
570    ///
571    /// # Examples
572    ///
573    /// A simple getter route for a state object.
574    ///
575    /// `api.toml`
576    ///
577    /// ```toml
578    /// [route.getstate]
579    /// PATH = ["/getstate"]
580    /// DOC = "Gets the current state."
581    /// ```
582    ///
583    /// ```
584    /// use futures::FutureExt;
585    /// # use tide_disco::Api;
586    /// # use vbs::version::StaticVersion;
587    ///
588    /// type State = u64;
589    /// type StaticVer01 = StaticVersion<0, 1>;
590    ///
591    /// # fn ex(api: &mut Api<State, (), StaticVer01>) {
592    /// api.at("getstate", |req, state| async { Ok(*state) }.boxed());
593    /// # }
594    /// ```
595    ///
596    /// A counter endpoint which increments a mutable state. Notice how we use `METHOD = "POST"` to
597    /// ensure that the HTTP method for this route is compatible with mutable access.
598    ///
599    /// `api.toml`
600    ///
601    /// ```toml
602    /// [route.increment]
603    /// PATH = ["/increment"]
604    /// METHOD = "POST"
605    /// DOC = "Increment the current state and return the new value."
606    /// ```
607    ///
608    /// ```
609    /// use async_std::sync::Mutex;
610    /// use futures::FutureExt;
611    /// # use tide_disco::Api;
612    /// # use vbs::version::StaticVersion;
613    ///
614    /// type State = Mutex<u64>;
615    /// type StaticVer01 = StaticVersion<0, 1>;
616    ///
617    /// # fn ex(api: &mut Api<State, (), StaticVer01>) {
618    /// api.at("increment", |req, state| async {
619    ///     let mut guard = state.lock().await;
620    ///     *guard += 1;
621    ///     Ok(*guard)
622    /// }.boxed());
623    /// # }
624    /// ```
625    ///
626    /// # Warnings
627    /// The route will use the HTTP method specified in the TOML specification for the named route
628    /// (or GET if the method is not specified). Some HTTP methods imply constraints on mutability.
629    /// For example, GET routes must be "pure", and not mutate any server state. Violating this
630    /// constraint may lead to confusing and unpredictable behavior. If the `State` type has
631    /// interior mutability (for instance, [RwLock](async_std::sync::RwLock)) it is up to the
632    /// `handler` not to use the interior mutability if the HTTP method suggests it shouldn't.
633    ///
634    /// If you know the HTTP method when you are registering the route, we recommend you use the
635    /// safer versions of this function, which enforce the appropriate mutability constraints. For
636    /// example,
637    /// * [get](Self::get)
638    /// * [post](Self::post)
639    /// * [put](Self::put)
640    /// * [delete](Self::delete)
641    ///
642    /// # Errors
643    ///
644    /// If the route `name` does not exist in the API specification, or if the route already has a
645    /// handler registered, an error is returned. Note that all routes are initialized with a
646    /// default handler that echoes parameters and shows documentation, but this default handler can
647    /// replaced by this function without raising [ApiError::HandlerAlreadyRegistered].
648    ///
649    /// If the route `name` exists, but it is not an HTTP route (for example, `METHOD = "SOCKET"`
650    /// was used when defining the route in the API specification), [ApiError::IncorrectMethod] is
651    /// returned.
652    ///
653    /// # Limitations
654    ///
655    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
656    /// handler function is required to return a [BoxFuture].
657    pub fn at<F, T>(&mut self, name: &str, handler: F) -> Result<&mut Self, ApiError>
658    where
659        F: 'static + Send + Sync + Fn(RequestParams, &State) -> BoxFuture<'_, Result<T, Error>>,
660        T: Serialize,
661        State: 'static + Send + Sync,
662        VER: 'static + Send + Sync,
663    {
664        let route = self
665            .inner
666            .routes
667            .get_mut(name)
668            .ok_or(ApiError::UndefinedRoute)?;
669        if route.has_handler() {
670            return Err(ApiError::HandlerAlreadyRegistered);
671        }
672
673        if !route.method().is_http() {
674            return Err(ApiError::IncorrectMethod {
675                // Just pick any HTTP method as the expected method.
676                expected: Method::get(),
677                actual: route.method(),
678            });
679        }
680
681        // `set_fn_handler` only fails if the route is not an HTTP route; since we have already
682        // checked that it is, this cannot fail.
683        route
684            .set_fn_handler(handler, VER::instance())
685            .unwrap_or_else(|_| panic!("unexpected failure in set_fn_handler"));
686
687        Ok(self)
688    }
689
690    fn method_immutable<F, T>(
691        &mut self,
692        method: Method,
693        name: &str,
694        handler: F,
695    ) -> Result<&mut Self, ApiError>
696    where
697        F: 'static
698            + Send
699            + Sync
700            + Fn(RequestParams, &<State as ReadState>::State) -> BoxFuture<'_, Result<T, Error>>,
701        T: Serialize,
702        State: 'static + Send + Sync + ReadState,
703        VER: 'static + Send + Sync + StaticVersionType,
704    {
705        assert!(method.is_http() && !method.is_mutable());
706        let route = self
707            .inner
708            .routes
709            .get_mut(name)
710            .ok_or(ApiError::UndefinedRoute)?;
711        if route.method() != method {
712            return Err(ApiError::IncorrectMethod {
713                expected: method,
714                actual: route.method(),
715            });
716        }
717        if route.has_handler() {
718            return Err(ApiError::HandlerAlreadyRegistered);
719        }
720        // `set_handler` only fails if the route is not an HTTP route; since we have already checked
721        // that it is, this cannot fail.
722        route
723            .set_handler(ReadHandler::<_, VER>::from(handler))
724            .unwrap_or_else(|_| panic!("unexpected failure in set_handler"));
725        Ok(self)
726    }
727
728    /// Register a handler for a GET route.
729    ///
730    /// When the server receives a GET request whose URL matches the pattern of the route `name`,
731    /// `handler` will be invoked with the parameters of the request and immutable access to the
732    /// current state, and the result will be serialized into a response.
733    ///
734    /// The [ReadState] trait is used to acquire immutable access to the state, so the state
735    /// reference passed to `handler` is actually [`<State as ReadState>::State`](ReadState::State).
736    /// For example, if `State` is `RwLock<T>`, the lock will automatically be acquired for reading,
737    /// and the handler will be passed a `&T`.
738    ///
739    /// # Examples
740    ///
741    /// A simple getter route for a state object.
742    ///
743    /// `api.toml`
744    ///
745    /// ```toml
746    /// [route.getstate]
747    /// PATH = ["/getstate"]
748    /// DOC = "Gets the current state."
749    /// ```
750    ///
751    /// ```
752    /// use async_std::sync::RwLock;
753    /// use futures::FutureExt;
754    /// # use tide_disco::Api;
755    /// # use vbs::{Serializer, version::StaticVersion};
756    ///
757    /// type State = RwLock<u64>;
758    /// type StaticVer01 = StaticVersion<0, 1>;
759    ///
760    /// # fn ex(api: &mut Api<State, (), StaticVer01>) {
761    /// api.get("getstate", |req, state| async { Ok(*state) }.boxed());
762    /// # }
763    /// ```
764    ///
765    /// # Errors
766    ///
767    /// If the route `name` does not exist in the API specification, or if the route already has a
768    /// handler registered, an error is returned. Note that all routes are initialized with a
769    /// default handler that echoes parameters and shows documentation, but this default handler can
770    /// replaced by this function without raising [ApiError::HandlerAlreadyRegistered].
771    ///
772    /// If the route `name` exists, but the method is not GET (that is, `METHOD = "M"` was used in
773    /// the route definition in `api.toml`, with `M` other than `GET`) the error
774    /// [IncorrectMethod](ApiError::IncorrectMethod) is returned.
775    ///
776    /// # Limitations
777    ///
778    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
779    /// handler function is required to return a [BoxFuture].
780    pub fn get<F, T>(&mut self, name: &str, handler: F) -> Result<&mut Self, ApiError>
781    where
782        F: 'static
783            + Send
784            + Sync
785            + Fn(RequestParams, &<State as ReadState>::State) -> BoxFuture<'_, Result<T, Error>>,
786        T: Serialize,
787        State: 'static + Send + Sync + ReadState,
788        VER: 'static + Send + Sync,
789    {
790        self.method_immutable(Method::get(), name, handler)
791    }
792
793    fn method_mutable<F, T>(
794        &mut self,
795        method: Method,
796        name: &str,
797        handler: F,
798    ) -> Result<&mut Self, ApiError>
799    where
800        F: 'static
801            + Send
802            + Sync
803            + Fn(RequestParams, &mut <State as ReadState>::State) -> BoxFuture<'_, Result<T, Error>>,
804        T: Serialize,
805        State: 'static + Send + Sync + WriteState,
806        VER: 'static + Send + Sync,
807    {
808        assert!(method.is_http() && method.is_mutable());
809        let route = self
810            .inner
811            .routes
812            .get_mut(name)
813            .ok_or(ApiError::UndefinedRoute)?;
814        if route.method() != method {
815            return Err(ApiError::IncorrectMethod {
816                expected: method,
817                actual: route.method(),
818            });
819        }
820        if route.has_handler() {
821            return Err(ApiError::HandlerAlreadyRegistered);
822        }
823
824        // `set_handler` only fails if the route is not an HTTP route; since we have already checked
825        // that it is, this cannot fail.
826        route
827            .set_handler(WriteHandler::<_, VER>::from(handler))
828            .unwrap_or_else(|_| panic!("unexpected failure in set_handler"));
829        Ok(self)
830    }
831
832    /// Register a handler for a POST route.
833    ///
834    /// When the server receives a POST request whose URL matches the pattern of the route `name`,
835    /// `handler` will be invoked with the parameters of the request and exclusive, mutable access
836    /// to the current state, and the result will be serialized into a response.
837    ///
838    /// The [WriteState] trait is used to acquire mutable access to the state, so the state
839    /// reference passed to `handler` is actually [`<State as ReadState>::State`](ReadState::State).
840    /// For example, if `State` is `RwLock<T>`, the lock will automatically be acquired for writing,
841    /// and the handler will be passed a `&mut T`.
842    ///
843    /// # Examples
844    ///
845    /// A counter endpoint which increments the state and returns the new state.
846    ///
847    /// `api.toml`
848    ///
849    /// ```toml
850    /// [route.increment]
851    /// PATH = ["/increment"]
852    /// METHOD = "POST"
853    /// DOC = "Increment the current state and return the new value."
854    /// ```
855    ///
856    /// ```
857    /// use async_std::sync::RwLock;
858    /// use futures::FutureExt;
859    /// # use tide_disco::Api;
860    /// # use vbs::version::StaticVersion;
861    ///
862    /// type State = RwLock<u64>;
863    /// type StaticVer01 = StaticVersion<0, 1>;
864    ///
865    /// # fn ex(api: &mut Api<State, (), StaticVer01>) {
866    /// api.post("increment", |req, state| async {
867    ///     *state += 1;
868    ///     Ok(*state)
869    /// }.boxed());
870    /// # }
871    /// ```
872    ///
873    /// # Errors
874    ///
875    /// If the route `name` does not exist in the API specification, or if the route already has a
876    /// handler registered, an error is returned. Note that all routes are initialized with a
877    /// default handler that echoes parameters and shows documentation, but this default handler can
878    /// replaced by this function without raising [ApiError::HandlerAlreadyRegistered].
879    ///
880    /// If the route `name` exists, but the method is not POST (that is, `METHOD = "M"` was used in
881    /// the route definition in `api.toml`, with `M` other than `POST`) the error
882    /// [IncorrectMethod](ApiError::IncorrectMethod) is returned.
883    ///
884    /// # Limitations
885    ///
886    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
887    /// handler function is required to return a [BoxFuture].
888    pub fn post<F, T>(&mut self, name: &str, handler: F) -> Result<&mut Self, ApiError>
889    where
890        F: 'static
891            + Send
892            + Sync
893            + Fn(RequestParams, &mut <State as ReadState>::State) -> BoxFuture<'_, Result<T, Error>>,
894        T: Serialize,
895        State: 'static + Send + Sync + WriteState,
896        VER: 'static + Send + Sync,
897    {
898        self.method_mutable(Method::post(), name, handler)
899    }
900
901    /// Register a handler for a PUT route.
902    ///
903    /// When the server receives a PUT request whose URL matches the pattern of the route `name`,
904    /// `handler` will be invoked with the parameters of the request and exclusive, mutable access
905    /// to the current state, and the result will be serialized into a response.
906    ///
907    /// The [WriteState] trait is used to acquire mutable access to the state, so the state
908    /// reference passed to `handler` is actually [`<State as ReadState>::State`](ReadState::State).
909    /// For example, if `State` is `RwLock<T>`, the lock will automatically be acquired for writing,
910    /// and the handler will be passed a `&mut T`.
911    ///
912    /// # Examples
913    ///
914    /// An endpoint which replaces the current state with a new value.
915    ///
916    /// `api.toml`
917    ///
918    /// ```toml
919    /// [route.replace]
920    /// PATH = ["/replace/:new_state"]
921    /// METHOD = "PUT"
922    /// ":new_state" = "Integer"
923    /// DOC = "Set the state to `:new_state`."
924    /// ```
925    ///
926    /// ```
927    /// use async_std::sync::RwLock;
928    /// use futures::FutureExt;
929    /// # use tide_disco::Api;
930    /// # use vbs::version::StaticVersion;
931    ///
932    /// type State = RwLock<u64>;
933    /// type StaticVer01 = StaticVersion<0, 1>;
934    ///
935    /// # fn ex(api: &mut Api<State, tide_disco::RequestError, StaticVer01>) {
936    /// api.post("replace", |req, state| async move {
937    ///     *state = req.integer_param("new_state")?;
938    ///     Ok(())
939    /// }.boxed());
940    /// # }
941    /// ```
942    ///
943    /// # Errors
944    ///
945    /// If the route `name` does not exist in the API specification, or if the route already has a
946    /// handler registered, an error is returned. Note that all routes are initialized with a
947    /// default handler that echoes parameters and shows documentation, but this default handler can
948    /// replaced by this function without raising [ApiError::HandlerAlreadyRegistered].
949    ///
950    /// If the route `name` exists, but the method is not PUT (that is, `METHOD = "M"` was used in
951    /// the route definition in `api.toml`, with `M` other than `PUT`) the error
952    /// [IncorrectMethod](ApiError::IncorrectMethod) is returned.
953    ///
954    /// # Limitations
955    ///
956    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
957    /// handler function is required to return a [BoxFuture].
958    pub fn put<F, T>(&mut self, name: &str, handler: F) -> Result<&mut Self, ApiError>
959    where
960        F: 'static
961            + Send
962            + Sync
963            + Fn(RequestParams, &mut <State as ReadState>::State) -> BoxFuture<'_, Result<T, Error>>,
964        T: Serialize,
965        State: 'static + Send + Sync + WriteState,
966        VER: 'static + Send + Sync,
967    {
968        self.method_mutable(Method::put(), name, handler)
969    }
970
971    /// Register a handler for a DELETE route.
972    ///
973    /// When the server receives a DELETE request whose URL matches the pattern of the route `name`,
974    /// `handler` will be invoked with the parameters of the request and exclusive, mutable access
975    /// to the current state, and the result will be serialized into a response.
976    ///
977    /// The [WriteState] trait is used to acquire mutable access to the state, so the state
978    /// reference passed to `handler` is actually [`<State as ReadState>::State`](ReadState::State).
979    /// For example, if `State` is `RwLock<T>`, the lock will automatically be acquired for writing,
980    /// and the handler will be passed a `&mut T`.
981    ///
982    /// # Examples
983    ///
984    /// An endpoint which clears the current state.
985    ///
986    /// `api.toml`
987    ///
988    /// ```toml
989    /// [route.state]
990    /// PATH = ["/state"]
991    /// METHOD = "DELETE"
992    /// DOC = "Clear the state."
993    /// ```
994    ///
995    /// ```
996    /// use async_std::sync::RwLock;
997    /// use futures::FutureExt;
998    /// # use tide_disco::Api;
999    /// # use vbs::version::StaticVersion;
1000    ///
1001    /// type State = RwLock<Option<u64>>;
1002    /// type StaticVer01 = StaticVersion<0, 1>;
1003    ///
1004    /// # fn ex(api: &mut Api<State, (), StaticVer01>) {
1005    /// api.delete("state", |req, state| async {
1006    ///     *state = None;
1007    ///     Ok(())
1008    /// }.boxed());
1009    /// # }
1010    /// ```
1011    ///
1012    /// # Errors
1013    ///
1014    /// If the route `name` does not exist in the API specification, or if the route already has a
1015    /// handler registered, an error is returned. Note that all routes are initialized with a
1016    /// default handler that echoes parameters and shows documentation, but this default handler can
1017    /// replaced by this function without raising [ApiError::HandlerAlreadyRegistered].
1018    ///
1019    /// If the route `name` exists, but the method is not DELETE (that is, `METHOD = "M"` was used
1020    /// in the route definition in `api.toml`, with `M` other than `DELETE`) the error
1021    /// [IncorrectMethod](ApiError::IncorrectMethod) is returned.
1022    ///
1023    /// # Limitations
1024    ///
1025    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
1026    /// handler function is required to return a [BoxFuture].
1027    pub fn delete<F, T>(&mut self, name: &str, handler: F) -> Result<&mut Self, ApiError>
1028    where
1029        F: 'static
1030            + Send
1031            + Sync
1032            + Fn(RequestParams, &mut <State as ReadState>::State) -> BoxFuture<'_, Result<T, Error>>,
1033        T: Serialize,
1034        State: 'static + Send + Sync + WriteState,
1035        VER: 'static + Send + Sync,
1036    {
1037        self.method_mutable(Method::delete(), name, handler)
1038    }
1039
1040    /// Register a handler for a SOCKET route.
1041    ///
1042    /// When the server receives any request whose URL matches the pattern for this route and which
1043    /// includes the WebSockets upgrade headers, the server will negotiate a protocol upgrade with
1044    /// the client, establishing a WebSockets connection, and then invoke `handler`. `handler` will
1045    /// be given the parameters of the request which initiated the connection and a reference to the
1046    /// application state, as well as a [Connection](socket::Connection) object which it can then
1047    /// use for asynchronous, bi-directional communication with the client.
1048    ///
1049    /// The server side of the connection will remain open as long as the future returned by
1050    /// `handler` is remains unresolved. The handler can terminate the connection by returning. If
1051    /// it returns an error, the error message will be included in the
1052    /// [CloseFrame](tide_websockets::tungstenite::protocol::CloseFrame) sent to the client when
1053    /// tearing down the connection.
1054    ///
1055    /// # Examples
1056    ///
1057    /// A socket endpoint which receives amounts from the client and returns a running sum.
1058    ///
1059    /// `api.toml`
1060    ///
1061    /// ```toml
1062    /// [route.sum]
1063    /// PATH = ["/sum"]
1064    /// METHOD = "SOCKET"
1065    /// DOC = "Stream a running sum."
1066    /// ```
1067    ///
1068    /// ```
1069    /// use futures::{FutureExt, SinkExt, StreamExt};
1070    /// use tide_disco::{error::ServerError, socket::Connection, Api};
1071    /// # use vbs::version::StaticVersion;
1072    ///
1073    /// # fn ex(api: &mut Api<(), ServerError, StaticVersion<0, 1>>) {
1074    /// api.socket("sum", |_req, mut conn: Connection<i32, i32, ServerError, StaticVersion<0, 1>>, _state| async move {
1075    ///     let mut sum = 0;
1076    ///     while let Some(amount) = conn.next().await {
1077    ///         sum += amount?;
1078    ///         conn.send(&sum).await?;
1079    ///     }
1080    ///     Ok(())
1081    /// }.boxed());
1082    /// # }
1083    /// ```
1084    ///
1085    /// In some cases, it may be desirable to handle messages to and from the client in separate
1086    /// tasks. There are two ways of doing this:
1087    ///
1088    /// ## Split the connection into separate stream and sink
1089    ///
1090    /// ```
1091    /// use async_std::task::spawn;
1092    /// use futures::{future::{join, FutureExt}, sink::SinkExt, stream::StreamExt};
1093    /// use tide_disco::{error::ServerError, socket::Connection, Api};
1094    /// # use vbs::version::StaticVersion;
1095    ///
1096    /// # fn ex(api: &mut Api<(), ServerError, StaticVersion<0, 1>>) {
1097    /// api.socket("endpoint", |_req, mut conn: Connection<i32, i32, ServerError, StaticVersion<0, 1>>, _state| async move {
1098    ///     let (mut sink, mut stream) = conn.split();
1099    ///     let recv = spawn(async move {
1100    ///         while let Some(Ok(msg)) = stream.next().await {
1101    ///             // Handle message from client.
1102    ///         }
1103    ///     });
1104    ///     let send = spawn(async move {
1105    ///         loop {
1106    ///             let msg = // get message to send to client
1107    /// #               0;
1108    ///             sink.send(msg).await;
1109    ///         }
1110    ///     });
1111    ///
1112    ///     join(send, recv).await;
1113    ///     Ok(())
1114    /// }.boxed());
1115    /// # }
1116    /// ```
1117    ///
1118    /// This approach requires messages to be sent to the client by value, consuming the message.
1119    /// This is because, if we were to use the `Sync<&ToClient>` implementation for `Connection`,
1120    /// the lifetime for `&ToClient` would be fixed after `split` is called, since the lifetime
1121    /// appears in the return type, `SplitSink<Connection<...>, &ToClient>`. Thus, this lifetime
1122    /// outlives any scoped local variables created after the `split` call, such as `msg` in the
1123    /// `loop`.
1124    ///
1125    /// If we want to use the message after sending it to the client, we would have to clone it,
1126    /// which may be inefficient or impossible. Thus, there is another approach:
1127    ///
1128    /// ## Clone the connection
1129    ///
1130    /// ```
1131    /// use async_std::task::spawn;
1132    /// use futures::{future::{join, FutureExt}, sink::SinkExt, stream::StreamExt};
1133    /// use tide_disco::{error::ServerError, socket::Connection, Api};
1134    /// # use vbs::version::StaticVersion;
1135    ///
1136    /// # fn ex(api: &mut Api<(), ServerError, StaticVersion<0, 1>>) {
1137    /// api.socket("endpoint", |_req, mut conn: Connection<i32, i32, ServerError, StaticVersion<0, 1>>, _state| async move {
1138    ///     let recv = {
1139    ///         let mut conn = conn.clone();
1140    ///         spawn(async move {
1141    ///             while let Some(Ok(msg)) = conn.next().await {
1142    ///                 // Handle message from client.
1143    ///             }
1144    ///         })
1145    ///     };
1146    ///     let send = spawn(async move {
1147    ///         loop {
1148    ///             let msg = // get message to send to client
1149    /// #               0;
1150    ///             conn.send(&msg).await;
1151    ///             // msg is still live at this point.
1152    ///             drop(msg);
1153    ///         }
1154    ///     });
1155    ///
1156    ///     join(send, recv).await;
1157    ///     Ok(())
1158    /// }.boxed());
1159    /// # }
1160    /// ```
1161    ///
1162    /// Depending on the exact situation, this method may end up being more verbose than the
1163    /// previous example. But it allows us to retain the higher-ranked trait bound `conn: for<'a>
1164    /// Sink<&'a ToClient>` instead of fixing the lifetime, which can prevent an unnecessary clone
1165    /// in certain situations.
1166    ///
1167    /// # Errors
1168    ///
1169    /// If the route `name` does not exist in the API specification, or if the route already has a
1170    /// handler registered, an error is returned. Note that all routes are initialized with a
1171    /// default handler that echoes parameters and shows documentation, but this default handler can
1172    /// replaced by this function without raising [ApiError::HandlerAlreadyRegistered].
1173    ///
1174    /// If the route `name` exists, but the method is not SOCKET (that is, `METHOD = "M"` was used
1175    /// in the route definition in `api.toml`, with `M` other than `SOCKET`) the error
1176    /// [IncorrectMethod](ApiError::IncorrectMethod) is returned.
1177    ///
1178    /// # Limitations
1179    ///
1180    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
1181    /// handler function is required to return a [BoxFuture].
1182    pub fn socket<F, ToClient, FromClient>(
1183        &mut self,
1184        name: &str,
1185        handler: F,
1186    ) -> Result<&mut Self, ApiError>
1187    where
1188        F: 'static
1189            + Send
1190            + Sync
1191            + Fn(
1192                RequestParams,
1193                socket::Connection<ToClient, FromClient, Error, VER>,
1194                &State,
1195            ) -> BoxFuture<'_, Result<(), Error>>,
1196        ToClient: 'static + Serialize + ?Sized,
1197        FromClient: 'static + DeserializeOwned,
1198        State: 'static + Send + Sync,
1199        Error: 'static + Send + Display,
1200    {
1201        self.register_socket_handler(name, socket::handler(handler))
1202    }
1203
1204    /// Register a uni-directional handler for a SOCKET route.
1205    ///
1206    /// This function is very similar to [socket](Self::socket), but it permits the handler only to
1207    /// send messages to the client, not to receive messages back. As such, the handler does not
1208    /// take a [Connection](socket::Connection). Instead, it simply returns a stream of messages
1209    /// which are forwarded to the client as they are generated. If the stream ever yields an error,
1210    /// the error is propagated to the client and then the connection is closed.
1211    ///
1212    /// This function can be simpler to use than [socket](Self::socket) in case the handler does not
1213    /// need to receive messages from the client.
1214    pub fn stream<F, Msg>(&mut self, name: &str, handler: F) -> Result<&mut Self, ApiError>
1215    where
1216        F: 'static + Send + Sync + Fn(RequestParams, &State) -> BoxStream<Result<Msg, Error>>,
1217        Msg: 'static + Serialize + Send + Sync,
1218        State: 'static + Send + Sync,
1219        Error: 'static + Send + Display,
1220        VER: 'static + Send + Sync,
1221    {
1222        self.register_socket_handler(name, socket::stream_handler::<_, _, _, _, VER>(handler))
1223    }
1224
1225    fn register_socket_handler(
1226        &mut self,
1227        name: &str,
1228        handler: socket::Handler<State, Error>,
1229    ) -> Result<&mut Self, ApiError> {
1230        let route = self
1231            .inner
1232            .routes
1233            .get_mut(name)
1234            .ok_or(ApiError::UndefinedRoute)?;
1235        if route.method() != Method::Socket {
1236            return Err(ApiError::IncorrectMethod {
1237                expected: Method::Socket,
1238                actual: route.method(),
1239            });
1240        }
1241        if route.has_handler() {
1242            return Err(ApiError::HandlerAlreadyRegistered);
1243        }
1244
1245        // `set_handler` only fails if the route is not a socket route; since we have already
1246        // checked that it is, this cannot fail.
1247        route
1248            .set_socket_handler(handler)
1249            .unwrap_or_else(|_| panic!("unexpected failure in set_socket_handler"));
1250        Ok(self)
1251    }
1252
1253    /// Register a handler for a METRICS route.
1254    ///
1255    /// When the server receives any request whose URL matches the pattern for this route and whose
1256    /// headers indicate it is a request for metrics, the server will invoke this `handler` instead
1257    /// of the regular HTTP handler for the endpoint. Instead of returning a typed object to
1258    /// serialize, `handler` will return a [Metrics] object which will be serialized to plaintext
1259    /// using the Prometheus format.
1260    ///
1261    /// A request is considered a request for metrics, for the purpose of dispatching to this
1262    /// handler, if the method is GET and the `Accept` header specifies `text/plain` as a better
1263    /// response type than `application/json` and `application/octet-stream` (other Tide Disco
1264    /// handlers respond to the content types `application/json` or `application/octet-stream`). As
1265    /// a special case, a request with no `Accept` header or `Accept: *` will return metrics when
1266    /// there is a metrics route matching the request URL, since metrics are given priority over
1267    /// other content types when multiple routes match the URL.
1268    ///
1269    /// # Examples
1270    ///
1271    /// A metrics endpoint which keeps track of how many times it has been called.
1272    ///
1273    /// `api.toml`
1274    ///
1275    /// ```toml
1276    /// [route.metrics]
1277    /// PATH = ["/metrics"]
1278    /// METHOD = "METRICS"
1279    /// DOC = "Export Prometheus metrics."
1280    /// ```
1281    ///
1282    /// ```
1283    /// # use async_std::sync::Mutex;
1284    /// # use futures::FutureExt;
1285    /// # use tide_disco::{api::{Api, ApiError}, error::ServerError};
1286    /// # use std::borrow::Cow;
1287    /// # use vbs::version::StaticVersion;
1288    /// use prometheus::{Counter, Registry};
1289    ///
1290    /// struct State {
1291    ///     counter: Counter,
1292    ///     metrics: Registry,
1293    /// }
1294    /// type StaticVer01 = StaticVersion<0, 1>;
1295    ///
1296    /// # fn ex(_api: Api<Mutex<State>, ServerError, StaticVer01>) -> Result<(), ApiError> {
1297    /// let mut api: Api<Mutex<State>, ServerError, StaticVer01>;
1298    /// # api = _api;
1299    /// api.metrics("metrics", |_req, state| async move {
1300    ///     state.counter.inc();
1301    ///     Ok(Cow::Borrowed(&state.metrics))
1302    /// }.boxed())?;
1303    /// # Ok(())
1304    /// # }
1305    /// ```
1306    //
1307    /// # Errors
1308    ///
1309    /// If the route `name` does not exist in the API specification, or if the route already has a
1310    /// handler registered, an error is returned. Note that all routes are initialized with a
1311    /// default handler that echoes parameters and shows documentation, but this default handler can
1312    /// replaced by this function without raising [ApiError::HandlerAlreadyRegistered].
1313    ///
1314    /// If the route `name` exists, but the method is not METRICS (that is, `METHOD = "M"` was used
1315    /// in the route definition in `api.toml`, with `M` other than `METRICS`) the error
1316    /// [IncorrectMethod](ApiError::IncorrectMethod) is returned.
1317    ///
1318    /// # Limitations
1319    ///
1320    /// [Like many function parameters](crate#boxed-futures) in [tide_disco](crate), the
1321    /// handler function is required to return a [BoxFuture].
1322    pub fn metrics<F, T>(&mut self, name: &str, handler: F) -> Result<&mut Self, ApiError>
1323    where
1324        F: 'static
1325            + Send
1326            + Sync
1327            + Fn(RequestParams, &State::State) -> BoxFuture<Result<Cow<T>, Error>>,
1328        T: 'static + Clone + Metrics,
1329        State: 'static + Send + Sync + ReadState,
1330        Error: 'static,
1331        VER: 'static + Send + Sync,
1332    {
1333        let route = self
1334            .inner
1335            .routes
1336            .get_mut(name)
1337            .ok_or(ApiError::UndefinedRoute)?;
1338        if route.method() != Method::Metrics {
1339            return Err(ApiError::IncorrectMethod {
1340                expected: Method::Metrics,
1341                actual: route.method(),
1342            });
1343        }
1344        if route.has_handler() {
1345            return Err(ApiError::HandlerAlreadyRegistered);
1346        }
1347        // `set_metrics_handler` only fails if the route is not a metrics route; since we have
1348        // already checked that it is, this cannot fail.
1349        route
1350            .set_metrics_handler(handler)
1351            .unwrap_or_else(|_| panic!("unexpected failure in set_metrics_handler"));
1352        Ok(self)
1353    }
1354
1355    /// Set the health check handler for this API.
1356    ///
1357    /// This overrides the existing handler. If `health_check` has not yet been called, the default
1358    /// handler is one which simply returns `Health::default()`.
1359    pub fn with_health_check<H>(
1360        &mut self,
1361        handler: impl 'static + Send + Sync + Fn(&State) -> BoxFuture<H>,
1362    ) -> &mut Self
1363    where
1364        State: 'static + Send + Sync,
1365        H: 'static + HealthCheck,
1366        VER: 'static + Send + Sync,
1367    {
1368        self.inner.health_check = route::health_check_handler::<_, _, VER>(handler);
1369        self
1370    }
1371
1372    /// Create a new [Api] which is just like this one, except has a transformed `Error` type.
1373    pub(crate) fn map_err<Error2>(
1374        self,
1375        f: impl 'static + Clone + Send + Sync + Fn(Error) -> Error2,
1376    ) -> Api<State, Error2, VER>
1377    where
1378        Error: 'static + Send + Sync,
1379        Error2: 'static,
1380        State: 'static + Send + Sync,
1381    {
1382        Api {
1383            inner: ApiInner {
1384                meta: self.inner.meta,
1385                name: self.inner.name,
1386                routes: self
1387                    .inner
1388                    .routes
1389                    .into_iter()
1390                    .map(|(name, route)| (name, route.map_err(f.clone())))
1391                    .collect(),
1392                routes_by_path: self.inner.routes_by_path,
1393                health_check: self.inner.health_check,
1394                api_version: self.inner.api_version,
1395                error_handler: None,
1396                version_handler: self.inner.version_handler,
1397                public: self.inner.public,
1398                short_description: self.inner.short_description,
1399                long_description: self.inner.long_description,
1400            },
1401            _version: Default::default(),
1402        }
1403    }
1404
1405    pub(crate) fn into_inner(mut self) -> ApiInner<State, Error>
1406    where
1407        Error: crate::Error,
1408    {
1409        // This `into_inner` finalizes the error type for the API. At this point, ensure
1410        // `error_handler` is set.
1411        self.inner.error_handler = Some(error_handler::<Error, VER>());
1412        self.inner
1413    }
1414
1415    fn default_health_check(req: RequestParams, _state: &State) -> BoxFuture<'_, tide::Response> {
1416        async move {
1417            // If there is no healthcheck handler registered, just return [HealthStatus::Available]
1418            // by default; after all, if this handler is getting hit at all, the service must be up.
1419            route::health_check_response::<_, VER>(
1420                &req.accept().unwrap_or_else(|_| {
1421                    // The healthcheck endpoint is not allowed to fail, so just use the default
1422                    // content type if we can't parse the Accept header.
1423                    let mut accept = Accept::new();
1424                    accept.set_wildcard(true);
1425                    accept
1426                }),
1427                HealthStatus::Available,
1428            )
1429        }
1430        .boxed()
1431    }
1432}
1433
1434// `ReadHandler { handler }` essentially represents a handler function
1435// `move |req, state| async { state.read(|state| handler(req, state)).await.await }`. However, I
1436// cannot convince Rust that the future returned by this closure moves out of `req` while borrowing
1437// from `handler`, which is owned by the closure itself and thus outlives the closure body. This is
1438// partly due to the limitation where _all_ closure parameters must be captured either by value or
1439// by reference, and probably partly due to my lack of creativity. In any case, writing out the
1440// closure object and [Handler] implementation by hand seems to convince Rust that this code is
1441// memory safe.
1442struct ReadHandler<F, VER> {
1443    handler: F,
1444    _version: PhantomData<VER>,
1445}
1446
1447impl<F, VER> From<F> for ReadHandler<F, VER> {
1448    fn from(f: F) -> Self {
1449        Self {
1450            handler: f,
1451            _version: Default::default(),
1452        }
1453    }
1454}
1455
1456#[async_trait]
1457impl<State, Error, F, R, VER> Handler<State, Error> for ReadHandler<F, VER>
1458where
1459    F: 'static
1460        + Send
1461        + Sync
1462        + Fn(RequestParams, &<State as ReadState>::State) -> BoxFuture<'_, Result<R, Error>>,
1463    R: Serialize,
1464    State: 'static + Send + Sync + ReadState,
1465    VER: 'static + Send + Sync + StaticVersionType,
1466{
1467    async fn handle(
1468        &self,
1469        req: RequestParams,
1470        state: &State,
1471    ) -> Result<tide::Response, RouteError<Error>> {
1472        let accept = req.accept()?;
1473        response_from_result(
1474            &accept,
1475            state.read(|state| (self.handler)(req, state)).await,
1476            VER::instance(),
1477        )
1478    }
1479}
1480
1481// A manual closure that serves a similar purpose as [ReadHandler].
1482struct WriteHandler<F, VER> {
1483    handler: F,
1484    _version: PhantomData<VER>,
1485}
1486
1487impl<F, VER> From<F> for WriteHandler<F, VER> {
1488    fn from(f: F) -> Self {
1489        Self {
1490            handler: f,
1491            _version: Default::default(),
1492        }
1493    }
1494}
1495
1496#[async_trait]
1497impl<State, Error, F, R, VER> Handler<State, Error> for WriteHandler<F, VER>
1498where
1499    F: 'static
1500        + Send
1501        + Sync
1502        + Fn(RequestParams, &mut <State as ReadState>::State) -> BoxFuture<'_, Result<R, Error>>,
1503    R: Serialize,
1504    State: 'static + Send + Sync + WriteState,
1505    VER: 'static + Send + Sync + StaticVersionType,
1506{
1507    async fn handle(
1508        &self,
1509        req: RequestParams,
1510        state: &State,
1511    ) -> Result<tide::Response, RouteError<Error>> {
1512        let accept = req.accept()?;
1513        response_from_result(
1514            &accept,
1515            state.write(|state| (self.handler)(req, state)).await,
1516            VER::instance(),
1517        )
1518    }
1519}
1520
1521#[cfg(test)]
1522mod test {
1523    use crate::{
1524        App, StatusCode, Url,
1525        error::{Error, ServerError},
1526        healthcheck::HealthStatus,
1527        socket::Connection,
1528        testing::{Client, setup_test, test_ws_client, test_ws_client_with_headers},
1529    };
1530    use async_std::{sync::RwLock, task::spawn};
1531    use async_tungstenite::{
1532        WebSocketStream,
1533        tungstenite::{http::header::*, protocol::Message, protocol::frame::coding::CloseCode},
1534    };
1535    use futures::{
1536        AsyncRead, AsyncWrite, FutureExt, SinkExt, StreamExt,
1537        stream::{iter, once, repeat},
1538    };
1539    use portpicker::pick_unused_port;
1540    use prometheus::{Counter, Registry};
1541    use std::borrow::Cow;
1542    use toml::toml;
1543    use vbs::{
1544        BinarySerializer, Serializer,
1545        version::{StaticVersion, StaticVersionType},
1546    };
1547
1548    #[cfg(windows)]
1549    use async_tungstenite::tungstenite::Error as WsError;
1550    #[cfg(windows)]
1551    use std::io::ErrorKind;
1552
1553    type StaticVer01 = StaticVersion<0, 1>;
1554    type SerializerV01 = Serializer<StaticVersion<0, 1>>;
1555
1556    async fn check_stream_closed<S>(mut conn: WebSocketStream<S>)
1557    where
1558        S: AsyncRead + AsyncWrite + Unpin,
1559    {
1560        let msg = conn.next().await;
1561
1562        #[cfg(not(windows))]
1563        assert!(msg.is_none(), "{:?}", msg);
1564
1565        // Windows doesn't handle shutdown very gracefully.
1566        #[cfg(windows)]
1567        match msg {
1568            None => {}
1569            Some(Err(WsError::Io(err))) if err.kind() == ErrorKind::ConnectionAborted => {}
1570            msg => panic!(
1571                "expected end of stream or ConnectionAborted error, got {:?}",
1572                msg
1573            ),
1574        }
1575    }
1576
1577    #[async_std::test]
1578    async fn test_socket_endpoint() {
1579        setup_test();
1580
1581        let mut app = App::<_, ServerError>::with_state(RwLock::new(()));
1582        let api_toml = toml! {
1583            [meta]
1584            FORMAT_VERSION = "0.1.0"
1585
1586            [route.echo]
1587            PATH = ["/echo"]
1588            METHOD = "SOCKET"
1589
1590            [route.once]
1591            PATH = ["/once"]
1592            METHOD = "SOCKET"
1593
1594            [route.error]
1595            PATH = ["/error"]
1596            METHOD = "SOCKET"
1597        };
1598        {
1599            let mut api = app
1600                .module::<ServerError, StaticVer01>("mod", api_toml)
1601                .unwrap();
1602            api.socket(
1603                "echo",
1604                |_req, mut conn: Connection<String, String, _, StaticVer01>, _state| {
1605                    async move {
1606                        while let Some(msg) = conn.next().await {
1607                            conn.send(&msg?).await?;
1608                        }
1609                        Ok(())
1610                    }
1611                    .boxed()
1612                },
1613            )
1614            .unwrap()
1615            .socket(
1616                "once",
1617                |_req, mut conn: Connection<str, (), _, StaticVer01>, _state| {
1618                    async move {
1619                        conn.send("msg").boxed().await?;
1620                        Ok(())
1621                    }
1622                    .boxed()
1623                },
1624            )
1625            .unwrap()
1626            .socket(
1627                "error",
1628                |_req, _conn: Connection<(), (), _, StaticVer01>, _state| {
1629                    async move {
1630                        Err(ServerError::catch_all(
1631                            StatusCode::INTERNAL_SERVER_ERROR,
1632                            "an error message".to_string(),
1633                        ))
1634                    }
1635                    .boxed()
1636                },
1637            )
1638            .unwrap();
1639        }
1640        let port = pick_unused_port().unwrap();
1641        let url: Url = format!("http://localhost:{}", port).parse().unwrap();
1642        spawn(app.serve(format!("0.0.0.0:{}", port), StaticVer01::instance()));
1643
1644        // Create a client that accepts JSON messages.
1645        let mut conn = test_ws_client_with_headers(
1646            url.join("mod/echo").unwrap(),
1647            &[(ACCEPT, "application/json")],
1648        )
1649        .await;
1650
1651        // Send a JSON message.
1652        conn.send(Message::Text(serde_json::to_string("hello").unwrap()))
1653            .await
1654            .unwrap();
1655        assert_eq!(
1656            conn.next().await.unwrap().unwrap(),
1657            Message::Text(serde_json::to_string("hello").unwrap())
1658        );
1659
1660        // Send a binary message.
1661        conn.send(Message::Binary(
1662            SerializerV01::serialize("goodbye").unwrap(),
1663        ))
1664        .await
1665        .unwrap();
1666        assert_eq!(
1667            conn.next().await.unwrap().unwrap(),
1668            Message::Text(serde_json::to_string("goodbye").unwrap())
1669        );
1670
1671        // Create a client that accepts binary messages.
1672        let mut conn = test_ws_client_with_headers(
1673            url.join("mod/echo").unwrap(),
1674            &[(ACCEPT, "application/octet-stream")],
1675        )
1676        .await;
1677
1678        // Send a JSON message.
1679        conn.send(Message::Text(serde_json::to_string("hello").unwrap()))
1680            .await
1681            .unwrap();
1682        assert_eq!(
1683            conn.next().await.unwrap().unwrap(),
1684            Message::Binary(SerializerV01::serialize("hello").unwrap())
1685        );
1686
1687        // Send a binary message.
1688        conn.send(Message::Binary(
1689            SerializerV01::serialize("goodbye").unwrap(),
1690        ))
1691        .await
1692        .unwrap();
1693        assert_eq!(
1694            conn.next().await.unwrap().unwrap(),
1695            Message::Binary(SerializerV01::serialize("goodbye").unwrap())
1696        );
1697
1698        // Test a stream that exits normally.
1699        let mut conn = test_ws_client(url.join("mod/once").unwrap()).await;
1700        assert_eq!(
1701            conn.next().await.unwrap().unwrap(),
1702            Message::Text(serde_json::to_string("msg").unwrap())
1703        );
1704        match conn.next().await.unwrap().unwrap() {
1705            Message::Close(None) => {}
1706            msg => panic!("expected normal close frame, got {:?}", msg),
1707        };
1708        check_stream_closed(conn).await;
1709
1710        // Test a stream that errors.
1711        let mut conn = test_ws_client(url.join("mod/error").unwrap()).await;
1712        match conn.next().await.unwrap().unwrap() {
1713            Message::Close(Some(frame)) => {
1714                assert_eq!(frame.code, CloseCode::Error);
1715                assert_eq!(frame.reason, "Error 500: an error message");
1716            }
1717            msg => panic!("expected error close frame, got {:?}", msg),
1718        }
1719        check_stream_closed(conn).await;
1720    }
1721
1722    #[async_std::test]
1723    async fn test_stream_endpoint() {
1724        setup_test();
1725
1726        let mut app = App::<_, ServerError>::with_state(RwLock::new(()));
1727        let api_toml = toml! {
1728            [meta]
1729            FORMAT_VERSION = "0.1.0"
1730
1731            [route.nat]
1732            PATH = ["/nat"]
1733            METHOD = "SOCKET"
1734
1735            [route.once]
1736            PATH = ["/once"]
1737            METHOD = "SOCKET"
1738
1739            [route.error]
1740            PATH = ["/error"]
1741            METHOD = "SOCKET"
1742        };
1743        {
1744            let mut api = app
1745                .module::<ServerError, StaticVer01>("mod", api_toml)
1746                .unwrap();
1747            api.stream("nat", |_req, _state| iter(0..).map(Ok).boxed())
1748                .unwrap()
1749                .stream("once", |_req, _state| once(async { Ok(0) }).boxed())
1750                .unwrap()
1751                .stream::<_, ()>("error", |_req, _state| {
1752                    // We intentionally return a stream that never terminates, to check that simply
1753                    // yielding an error causes the connection to terminate.
1754                    repeat(Err(ServerError::catch_all(
1755                        StatusCode::INTERNAL_SERVER_ERROR,
1756                        "an error message".to_string(),
1757                    )))
1758                    .boxed()
1759                })
1760                .unwrap();
1761        }
1762        let port = pick_unused_port().unwrap();
1763        let url: Url = format!("http://localhost:{}", port).parse().unwrap();
1764        spawn(app.serve(format!("0.0.0.0:{}", port), StaticVer01::instance()));
1765
1766        // Consume the `nat` stream.
1767        let mut conn = test_ws_client(url.join("mod/nat").unwrap()).await;
1768        for i in 0..100 {
1769            assert_eq!(
1770                conn.next().await.unwrap().unwrap(),
1771                Message::Text(serde_json::to_string(&i).unwrap())
1772            );
1773        }
1774
1775        // Test a finite stream.
1776        let mut conn = test_ws_client(url.join("mod/once").unwrap()).await;
1777        assert_eq!(
1778            conn.next().await.unwrap().unwrap(),
1779            Message::Text(serde_json::to_string(&0).unwrap())
1780        );
1781        match conn.next().await.unwrap().unwrap() {
1782            Message::Close(None) => {}
1783            msg => panic!("expected normal close frame, got {:?}", msg),
1784        }
1785        check_stream_closed(conn).await;
1786
1787        // Test a stream that errors.
1788        let mut conn = test_ws_client(url.join("mod/error").unwrap()).await;
1789        match conn.next().await.unwrap().unwrap() {
1790            Message::Close(Some(frame)) => {
1791                assert_eq!(frame.code, CloseCode::Error);
1792                assert_eq!(frame.reason, "Error 500: an error message");
1793            }
1794            msg => panic!("expected error close frame, got {:?}", msg),
1795        }
1796        check_stream_closed(conn).await;
1797    }
1798
1799    #[async_std::test]
1800    async fn test_custom_healthcheck() {
1801        setup_test();
1802
1803        let mut app = App::<_, ServerError>::with_state(HealthStatus::Available);
1804        let api_toml = toml! {
1805            [meta]
1806            FORMAT_VERSION = "0.1.0"
1807
1808            [route.dummy]
1809            PATH = ["/dummy"]
1810        };
1811        {
1812            let mut api = app
1813                .module::<ServerError, StaticVer01>("mod", api_toml)
1814                .unwrap();
1815            api.with_health_check(|state| async move { *state }.boxed());
1816        }
1817        let port = pick_unused_port().unwrap();
1818        let url: Url = format!("http://localhost:{}", port).parse().unwrap();
1819        spawn(app.serve(format!("0.0.0.0:{}", port), StaticVer01::instance()));
1820        let client = Client::new(url).await;
1821
1822        let res = client.get("/mod/healthcheck").send().await.unwrap();
1823        assert_eq!(res.status(), StatusCode::OK);
1824        assert_eq!(
1825            res.json::<HealthStatus>().await.unwrap(),
1826            HealthStatus::Available
1827        );
1828    }
1829
1830    #[async_std::test]
1831    async fn test_metrics_endpoint() {
1832        setup_test();
1833
1834        struct State {
1835            metrics: Registry,
1836            counter: Counter,
1837        }
1838
1839        let counter = Counter::new(
1840            "counter",
1841            "count of how many times metrics have been exported",
1842        )
1843        .unwrap();
1844        let metrics = Registry::new();
1845        metrics.register(Box::new(counter.clone())).unwrap();
1846        let state = State { metrics, counter };
1847
1848        let mut app = App::<_, ServerError>::with_state(RwLock::new(state));
1849        let api_toml = toml! {
1850            [meta]
1851            FORMAT_VERSION = "0.1.0"
1852
1853            [route.metrics]
1854            PATH = ["/metrics"]
1855            METHOD = "METRICS"
1856        };
1857        {
1858            let mut api = app
1859                .module::<ServerError, StaticVer01>("mod", api_toml)
1860                .unwrap();
1861            api.metrics("metrics", |_req, state| {
1862                async move {
1863                    state.counter.inc();
1864                    Ok(Cow::Borrowed(&state.metrics))
1865                }
1866                .boxed()
1867            })
1868            .unwrap();
1869        }
1870        let port = pick_unused_port().unwrap();
1871        let url: Url = format!("http://localhost:{port}").parse().unwrap();
1872        spawn(app.serve(format!("0.0.0.0:{port}"), StaticVer01::instance()));
1873        let client = Client::new(url).await;
1874
1875        for i in 1..5 {
1876            tracing::info!("making metrics request {i}");
1877            let expected = format!(
1878                "# HELP counter count of how many times metrics have been exported\n# TYPE counter counter\ncounter {i}\n"
1879            );
1880            let res = client.get("mod/metrics").send().await.unwrap();
1881            assert_eq!(res.status(), StatusCode::OK);
1882            assert_eq!(res.text().await.unwrap(), expected);
1883        }
1884    }
1885}