diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index 7d2cf549..00000000 --- a/.rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -merge_imports = true diff --git a/.travis.yml b/.travis.yml index d148d7b4..e9812c08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,12 @@ language: "rust" +before_script: + - "rustup component add rustfmt" + - "rustup component add clippy" +script: + - "cargo fmt --all -- --check" + - "cargo clippy --all-targets --all-features -- -D warnings" + - "cargo build --verbose" + - "cargo test --verbose" notifications: email: false irc: diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 724ea00f..69fc7523 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,11 +1,20 @@ +//! Details of the `metadata` section of the procedural macro. + use syn::{punctuated::Pair, Expr, FieldValue, Lit, Member}; +/// The result of processing the `metadata` section of the macro. pub struct Metadata { + /// The description field. pub description: String, + /// The method field. pub method: String, + /// The name field. pub name: String, + /// The path field. pub path: String, + /// The rate_limited field. pub rate_limited: bool, + /// The description field. pub requires_authentication: bool, } @@ -101,7 +110,7 @@ impl From> for Metadata { } } - Metadata { + Self { description: description.expect("ruma_api! `metadata` is missing `description`"), method: method.expect("ruma_api! `metadata` is missing `method`"), name: name.expect("ruma_api! `metadata` is missing `name`"), diff --git a/src/api/mod.rs b/src/api/mod.rs index 77a14b4f..d8b3eb03 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,5 @@ +//! Details of the `ruma-api` procedural macro. + use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ @@ -12,6 +14,7 @@ mod response; use self::{metadata::Metadata, request::Request, response::Response}; +/// Removes `serde` attributes from struct fields. pub fn strip_serde_attrs(field: &Field) -> Field { let mut field = field.clone(); @@ -39,15 +42,19 @@ pub fn strip_serde_attrs(field: &Field) -> Field { field } +/// The result of processing the `ruma_api` macro, ready for output back to source code. pub struct Api { + /// The `metadata` section of the macro. metadata: Metadata, + /// The `request` section of the macro. request: Request, + /// The `response` section of the macro. response: Response, } impl From for Api { fn from(raw_api: RawApi) -> Self { - Api { + Self { metadata: raw_api.metadata.into(), request: raw_api.request.into(), response: raw_api.response.into(), @@ -88,7 +95,7 @@ impl ToTokens for Api { let request_path_init_fields = self.request.request_path_init_fields(); - let path_segments = path_str[1..].split('/').into_iter(); + let path_segments = path_str[1..].split('/'); let path_segment_push = path_segments.clone().map(|segment| { let arg = if segment.starts_with(':') { let path_var = &segment[1..]; @@ -450,6 +457,7 @@ impl ToTokens for Api { type Request = Request; type Response = Response; + /// Metadata for this endpoint. const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { description: #description, method: ::http::Method::#method, @@ -465,6 +473,7 @@ impl ToTokens for Api { } } +/// Custom keyword macros for syn. mod kw { use syn::custom_keyword; @@ -473,9 +482,13 @@ mod kw { custom_keyword!(response); } +/// The entire `ruma_api!` macro structure directly as it appears in the source code.. pub struct RawApi { + /// The `metadata` section of the macro. pub metadata: Vec, + /// The `request` section of the macro. pub request: Vec, + /// The `response` section of the macro. pub response: Vec, } @@ -493,7 +506,7 @@ impl Parse for RawApi { let response; braced!(response in input); - Ok(RawApi { + Ok(Self { metadata: metadata .parse_terminated::(FieldValue::parse)? .into_iter() diff --git a/src/api/request.rs b/src/api/request.rs index 07fee5dd..af2cb2d3 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,14 +1,19 @@ +//! Details of the `request` section of the procedural macro. + use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use crate::api::strip_serde_attrs; +/// The result of processing the `request` section of the macro. pub struct Request { + /// The fields of the request. fields: Vec, } impl Request { + /// Produces code to add necessary HTTP headers to an `http::Request`. pub fn add_headers_to_request(&self) -> TokenStream { let append_stmts = self.header_fields().map(|request_field| { let (field, header_name_string) = match request_field { @@ -33,6 +38,7 @@ impl Request { } } + /// Produces code to extract fields from the HTTP headers in an `http::Request`. pub fn parse_headers_from_request(&self) -> TokenStream { let fields = self.header_fields().map(|request_field| { let (field, header_name_string) = match request_field { @@ -56,43 +62,50 @@ impl Request { } } + /// Whether or not this request has any data in the HTTP body. pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } + /// Whether or not this request has any data in HTTP headers. pub fn has_header_fields(&self) -> bool { self.fields.iter().any(|field| field.is_header()) } + /// Whether or not this request has any data in the URL path. pub fn has_path_fields(&self) -> bool { self.fields.iter().any(|field| field.is_path()) } + /// Whether or not this request has any data in the query string. pub fn has_query_fields(&self) -> bool { self.fields.iter().any(|field| field.is_query()) } + /// Produces an iterator over all the header fields. pub fn header_fields(&self) -> impl Iterator { self.fields.iter().filter(|field| field.is_header()) } + /// Gets the number of path fields. pub fn path_field_count(&self) -> usize { self.fields.iter().filter(|field| field.is_path()).count() } + /// Gets the path field with the given name. pub fn path_field(&self, name: &str) -> Option<&Field> { self.fields .iter() - .flat_map(|f| f.field_(RequestFieldKind::Path)) + .flat_map(|f| f.field_of_kind(RequestFieldKind::Path)) .find(|field| { field .ident .as_ref() .expect("expected field to have an identifier") - .to_string() == name }) } + /// Returns the body field. pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { @@ -106,33 +119,41 @@ impl Request { None } + /// Produces code for a struct initializer for body fields on a variable named `request`. pub fn request_body_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Body, quote!(request)) } + /// Produces code for a struct initializer for path fields on a variable named `request`. pub fn request_path_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Path, quote!(request)) } + /// Produces code for a struct initializer for query string fields on a variable named `request`. pub fn request_query_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Query, quote!(request)) } + /// Produces code for a struct initializer for body fields on a variable named `request_body`. pub fn request_init_body_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Body, quote!(request_body)) } + /// Produces code for a struct initializer for query string fields on a variable named + /// `request_query`. pub fn request_init_query_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Query, quote!(request_query)) } + /// Produces code for a struct initializer for the given field kind to be accessed through the + /// given variable name. fn struct_init_fields( &self, request_field_kind: RequestFieldKind, src: TokenStream, ) -> TokenStream { let fields = self.fields.iter().filter_map(|f| { - f.field_(request_field_kind).map(|field| { + f.field_of_kind(request_field_kind).map(|field| { let field_name = field .ident .as_ref() @@ -222,7 +243,7 @@ impl From> for Request { RequestField::new(field_kind, field, header) }).collect(); - Request { fields } + Self { fields } } } @@ -345,16 +366,23 @@ impl ToTokens for Request { } } +/// The types of fields that a request can have. pub enum RequestField { + /// JSON data in the body of the request. Body(Field), + /// Data in an HTTP header. Header(Field, String), + /// A specific data type in the body of the request. NewtypeBody(Field), + /// Data that appears in the URL path. Path(Field), + /// Data that appears in the query string. Query(Field), } impl RequestField { - fn new(kind: RequestFieldKind, field: Field, header: Option) -> RequestField { + /// Creates a new `RequestField`. + fn new(kind: RequestFieldKind, field: Field, header: Option) -> Self { match kind { RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Header => { @@ -366,6 +394,7 @@ impl RequestField { } } + /// Gets the kind of the request field. fn kind(&self) -> RequestFieldKind { match *self { RequestField::Body(..) => RequestFieldKind::Body, @@ -376,33 +405,39 @@ impl RequestField { } } + /// Whether or not this request field is a body kind. fn is_body(&self) -> bool { self.kind() == RequestFieldKind::Body } + /// Whether or not this request field is a header kind. fn is_header(&self) -> bool { self.kind() == RequestFieldKind::Header } + /// Whether or not this request field is a path kind. fn is_path(&self) -> bool { self.kind() == RequestFieldKind::Path } + /// Whether or not this request field is a query string kind. fn is_query(&self) -> bool { self.kind() == RequestFieldKind::Query } + /// Gets the inner `Field` value. fn field(&self) -> &Field { match *self { - RequestField::Body(ref field) => field, - RequestField::Header(ref field, _) => field, - RequestField::NewtypeBody(ref field) => field, - RequestField::Path(ref field) => field, - RequestField::Query(ref field) => field, + RequestField::Body(ref field) + | RequestField::Header(ref field, _) + | RequestField::NewtypeBody(ref field) + | RequestField::Path(ref field) + | RequestField::Query(ref field) => field, } } - fn field_(&self, kind: RequestFieldKind) -> Option<&Field> { + /// Gets the inner `Field` value if it's of the provided kind. + fn field_of_kind(&self, kind: RequestFieldKind) -> Option<&Field> { if self.kind() == kind { Some(self.field()) } else { @@ -411,11 +446,17 @@ impl RequestField { } } +/// The types of fields that a request can have, without their values. #[derive(Clone, Copy, PartialEq, Eq)] enum RequestFieldKind { + /// See the similarly named variant of `RequestField`. Body, + /// See the similarly named variant of `RequestField`. Header, + /// See the similarly named variant of `RequestField`. NewtypeBody, + /// See the similarly named variant of `RequestField`. Path, + /// See the similarly named variant of `RequestField`. Query, } diff --git a/src/api/response.rs b/src/api/response.rs index 4b6a25e7..d860ed39 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,30 +1,39 @@ +//! Details of the `response` section of the procedural macro. + use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use crate::api::strip_serde_attrs; +/// The result of processing the `request` section of the macro. pub struct Response { + /// The fields of the response. fields: Vec, } impl Response { + /// Whether or not this response has any data in the HTTP body. pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } + /// Whether or not this response has any fields. pub fn has_fields(&self) -> bool { !self.fields.is_empty() } + /// Whether or not this response has any data in HTTP headers. pub fn has_header_fields(&self) -> bool { self.fields.iter().any(|field| field.is_header()) } + /// Whether or not this response has any data in the HTTP body. pub fn has_body(&self) -> bool { self.fields.iter().any(|field| !field.is_header()) } + /// Produces code for a request struct initializer. pub fn init_fields(&self) -> TokenStream { let fields = self .fields @@ -75,6 +84,7 @@ impl Response { } } + /// Produces code to add necessary HTTP headers to an `http::Response`. pub fn apply_header_fields(&self) -> TokenStream { let header_calls = self.fields.iter().filter_map(|response_field| { if let ResponseField::Header(ref field, ref header) = *response_field { @@ -98,8 +108,9 @@ impl Response { } } + /// Produces code to initialize the struct that will be used to create the response body. pub fn to_body(&self) -> TokenStream { - if let Some(ref field) = self.newtype_body_field() { + if let Some(field) = self.newtype_body_field() { let field_name = field .ident .as_ref() @@ -131,6 +142,7 @@ impl Response { } } + /// Gets the newtype body field, if this request has one. pub fn newtype_body_field(&self) -> Option<&Field> { for response_field in self.fields.iter() { match *response_field { @@ -217,7 +229,7 @@ impl From> for Response { } }).collect(); - Response { fields } + Self { fields } } } @@ -291,28 +303,35 @@ impl ToTokens for Response { } } +/// The types of fields that a response can have. pub enum ResponseField { + /// JSON data in the body of the response. Body(Field), + /// Data in an HTTP header. Header(Field, String), + /// A specific data type in the body of the response. NewtypeBody(Field), } impl ResponseField { + /// Gets the inner `Field` value. fn field(&self) -> &Field { match *self { - ResponseField::Body(ref field) => field, - ResponseField::Header(ref field, _) => field, - ResponseField::NewtypeBody(ref field) => field, + ResponseField::Body(ref field) + | ResponseField::Header(ref field, _) + | ResponseField::NewtypeBody(ref field) => field, } } + /// Whether or not this response field is a body kind. fn is_body(&self) -> bool { match *self { - ResponseField::Body(..) => true, + ResponseField::Body(_) => true, _ => false, } } + /// Whether or not this response field is a header kind. fn is_header(&self) -> bool { match *self { ResponseField::Header(..) => true, @@ -321,8 +340,12 @@ impl ResponseField { } } +/// The types of fields that a response can have, without their values. enum ResponseFieldKind { + /// See the similarly named variant of `ResponseField`. Body, + /// See the similarly named variant of `ResponseField`. Header, + /// See the similarly named variant of `ResponseField`. NewtypeBody, } diff --git a/src/lib.rs b/src/lib.rs index 8e703817..e260765e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,31 @@ //! //! See the documentation for the `ruma_api!` macro for usage details. -#![deny(missing_debug_implementations)] +#![deny( + missing_copy_implementations, + missing_debug_implementations, + // missing_docs, # Uncomment when https://github.com/rust-lang/rust/pull/60562 is released. + warnings +)] +#![warn( + clippy::empty_line_after_outer_attr, + clippy::expl_impl_clone_on_copy, + clippy::if_not_else, + clippy::items_after_statements, + clippy::match_same_arms, + clippy::mem_forget, + clippy::missing_docs_in_private_items, + clippy::multiple_inherent_impl, + clippy::mut_mut, + clippy::needless_borrow, + clippy::needless_continue, + clippy::single_match_else, + clippy::unicode_not_nfc, + clippy::use_self, + clippy::used_underscore_binding, + clippy::wrong_pub_self_convention, + clippy::wrong_self_convention +)] #![recursion_limit = "256"] extern crate proc_macro;