Add rustfmt and clippy to CI and address clippy warnings.

This commit is contained in:
Jimmy Cuadra 2019-06-02 17:35:26 -07:00
parent a92e71f873
commit 6a09f1f754
7 changed files with 140 additions and 23 deletions

View File

@ -1 +0,0 @@
merge_imports = true

View File

@ -1,4 +1,12 @@
language: "rust" 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: notifications:
email: false email: false
irc: irc:

View File

@ -1,11 +1,20 @@
//! Details of the `metadata` section of the procedural macro.
use syn::{punctuated::Pair, Expr, FieldValue, Lit, Member}; use syn::{punctuated::Pair, Expr, FieldValue, Lit, Member};
/// The result of processing the `metadata` section of the macro.
pub struct Metadata { pub struct Metadata {
/// The description field.
pub description: String, pub description: String,
/// The method field.
pub method: String, pub method: String,
/// The name field.
pub name: String, pub name: String,
/// The path field.
pub path: String, pub path: String,
/// The rate_limited field.
pub rate_limited: bool, pub rate_limited: bool,
/// The description field.
pub requires_authentication: bool, pub requires_authentication: bool,
} }
@ -101,7 +110,7 @@ impl From<Vec<FieldValue>> for Metadata {
} }
} }
Metadata { Self {
description: description.expect("ruma_api! `metadata` is missing `description`"), description: description.expect("ruma_api! `metadata` is missing `description`"),
method: method.expect("ruma_api! `metadata` is missing `method`"), method: method.expect("ruma_api! `metadata` is missing `method`"),
name: name.expect("ruma_api! `metadata` is missing `name`"), name: name.expect("ruma_api! `metadata` is missing `name`"),

View File

@ -1,3 +1,5 @@
//! Details of the `ruma-api` procedural macro.
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{ use syn::{
@ -12,6 +14,7 @@ mod response;
use self::{metadata::Metadata, request::Request, response::Response}; use self::{metadata::Metadata, request::Request, response::Response};
/// Removes `serde` attributes from struct fields.
pub fn strip_serde_attrs(field: &Field) -> Field { pub fn strip_serde_attrs(field: &Field) -> Field {
let mut field = field.clone(); let mut field = field.clone();
@ -39,15 +42,19 @@ pub fn strip_serde_attrs(field: &Field) -> Field {
field field
} }
/// The result of processing the `ruma_api` macro, ready for output back to source code.
pub struct Api { pub struct Api {
/// The `metadata` section of the macro.
metadata: Metadata, metadata: Metadata,
/// The `request` section of the macro.
request: Request, request: Request,
/// The `response` section of the macro.
response: Response, response: Response,
} }
impl From<RawApi> for Api { impl From<RawApi> for Api {
fn from(raw_api: RawApi) -> Self { fn from(raw_api: RawApi) -> Self {
Api { Self {
metadata: raw_api.metadata.into(), metadata: raw_api.metadata.into(),
request: raw_api.request.into(), request: raw_api.request.into(),
response: raw_api.response.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 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 path_segment_push = path_segments.clone().map(|segment| {
let arg = if segment.starts_with(':') { let arg = if segment.starts_with(':') {
let path_var = &segment[1..]; let path_var = &segment[1..];
@ -450,6 +457,7 @@ impl ToTokens for Api {
type Request = Request; type Request = Request;
type Response = Response; type Response = Response;
/// Metadata for this endpoint.
const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata {
description: #description, description: #description,
method: ::http::Method::#method, method: ::http::Method::#method,
@ -465,6 +473,7 @@ impl ToTokens for Api {
} }
} }
/// Custom keyword macros for syn.
mod kw { mod kw {
use syn::custom_keyword; use syn::custom_keyword;
@ -473,9 +482,13 @@ mod kw {
custom_keyword!(response); custom_keyword!(response);
} }
/// The entire `ruma_api!` macro structure directly as it appears in the source code..
pub struct RawApi { pub struct RawApi {
/// The `metadata` section of the macro.
pub metadata: Vec<FieldValue>, pub metadata: Vec<FieldValue>,
/// The `request` section of the macro.
pub request: Vec<Field>, pub request: Vec<Field>,
/// The `response` section of the macro.
pub response: Vec<Field>, pub response: Vec<Field>,
} }
@ -493,7 +506,7 @@ impl Parse for RawApi {
let response; let response;
braced!(response in input); braced!(response in input);
Ok(RawApi { Ok(Self {
metadata: metadata metadata: metadata
.parse_terminated::<FieldValue, Token![,]>(FieldValue::parse)? .parse_terminated::<FieldValue, Token![,]>(FieldValue::parse)?
.into_iter() .into_iter()

View File

@ -1,14 +1,19 @@
//! Details of the `request` section of the procedural macro.
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta};
use crate::api::strip_serde_attrs; use crate::api::strip_serde_attrs;
/// The result of processing the `request` section of the macro.
pub struct Request { pub struct Request {
/// The fields of the request.
fields: Vec<RequestField>, fields: Vec<RequestField>,
} }
impl Request { impl Request {
/// Produces code to add necessary HTTP headers to an `http::Request`.
pub fn add_headers_to_request(&self) -> TokenStream { pub fn add_headers_to_request(&self) -> TokenStream {
let append_stmts = self.header_fields().map(|request_field| { let append_stmts = self.header_fields().map(|request_field| {
let (field, header_name_string) = match 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 { pub fn parse_headers_from_request(&self) -> TokenStream {
let fields = self.header_fields().map(|request_field| { let fields = self.header_fields().map(|request_field| {
let (field, header_name_string) = match 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 { pub fn has_body_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_body()) 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 { pub fn has_header_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_header()) 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 { pub fn has_path_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_path()) 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 { pub fn has_query_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_query()) self.fields.iter().any(|field| field.is_query())
} }
/// Produces an iterator over all the header fields.
pub fn header_fields(&self) -> impl Iterator<Item = &RequestField> { pub fn header_fields(&self) -> impl Iterator<Item = &RequestField> {
self.fields.iter().filter(|field| field.is_header()) self.fields.iter().filter(|field| field.is_header())
} }
/// Gets the number of path fields.
pub fn path_field_count(&self) -> usize { pub fn path_field_count(&self) -> usize {
self.fields.iter().filter(|field| field.is_path()).count() 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> { pub fn path_field(&self, name: &str) -> Option<&Field> {
self.fields self.fields
.iter() .iter()
.flat_map(|f| f.field_(RequestFieldKind::Path)) .flat_map(|f| f.field_of_kind(RequestFieldKind::Path))
.find(|field| { .find(|field| {
field field
.ident .ident
.as_ref() .as_ref()
.expect("expected field to have an identifier") .expect("expected field to have an identifier")
.to_string()
== name == name
}) })
} }
/// Returns the body field.
pub fn newtype_body_field(&self) -> Option<&Field> { pub fn newtype_body_field(&self) -> Option<&Field> {
for request_field in self.fields.iter() { for request_field in self.fields.iter() {
match *request_field { match *request_field {
@ -106,33 +119,41 @@ impl Request {
None None
} }
/// Produces code for a struct initializer for body fields on a variable named `request`.
pub fn request_body_init_fields(&self) -> TokenStream { pub fn request_body_init_fields(&self) -> TokenStream {
self.struct_init_fields(RequestFieldKind::Body, quote!(request)) 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 { pub fn request_path_init_fields(&self) -> TokenStream {
self.struct_init_fields(RequestFieldKind::Path, quote!(request)) 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 { pub fn request_query_init_fields(&self) -> TokenStream {
self.struct_init_fields(RequestFieldKind::Query, quote!(request)) 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 { pub fn request_init_body_fields(&self) -> TokenStream {
self.struct_init_fields(RequestFieldKind::Body, quote!(request_body)) 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 { pub fn request_init_query_fields(&self) -> TokenStream {
self.struct_init_fields(RequestFieldKind::Query, quote!(request_query)) 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( fn struct_init_fields(
&self, &self,
request_field_kind: RequestFieldKind, request_field_kind: RequestFieldKind,
src: TokenStream, src: TokenStream,
) -> TokenStream { ) -> TokenStream {
let fields = self.fields.iter().filter_map(|f| { 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 let field_name = field
.ident .ident
.as_ref() .as_ref()
@ -222,7 +243,7 @@ impl From<Vec<Field>> for Request {
RequestField::new(field_kind, field, header) RequestField::new(field_kind, field, header)
}).collect(); }).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 { pub enum RequestField {
/// JSON data in the body of the request.
Body(Field), Body(Field),
/// Data in an HTTP header.
Header(Field, String), Header(Field, String),
/// A specific data type in the body of the request.
NewtypeBody(Field), NewtypeBody(Field),
/// Data that appears in the URL path.
Path(Field), Path(Field),
/// Data that appears in the query string.
Query(Field), Query(Field),
} }
impl RequestField { impl RequestField {
fn new(kind: RequestFieldKind, field: Field, header: Option<String>) -> RequestField { /// Creates a new `RequestField`.
fn new(kind: RequestFieldKind, field: Field, header: Option<String>) -> Self {
match kind { match kind {
RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Body => RequestField::Body(field),
RequestFieldKind::Header => { RequestFieldKind::Header => {
@ -366,6 +394,7 @@ impl RequestField {
} }
} }
/// Gets the kind of the request field.
fn kind(&self) -> RequestFieldKind { fn kind(&self) -> RequestFieldKind {
match *self { match *self {
RequestField::Body(..) => RequestFieldKind::Body, 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 { fn is_body(&self) -> bool {
self.kind() == RequestFieldKind::Body self.kind() == RequestFieldKind::Body
} }
/// Whether or not this request field is a header kind.
fn is_header(&self) -> bool { fn is_header(&self) -> bool {
self.kind() == RequestFieldKind::Header self.kind() == RequestFieldKind::Header
} }
/// Whether or not this request field is a path kind.
fn is_path(&self) -> bool { fn is_path(&self) -> bool {
self.kind() == RequestFieldKind::Path self.kind() == RequestFieldKind::Path
} }
/// Whether or not this request field is a query string kind.
fn is_query(&self) -> bool { fn is_query(&self) -> bool {
self.kind() == RequestFieldKind::Query self.kind() == RequestFieldKind::Query
} }
/// Gets the inner `Field` value.
fn field(&self) -> &Field { fn field(&self) -> &Field {
match *self { match *self {
RequestField::Body(ref field) => field, RequestField::Body(ref field)
RequestField::Header(ref field, _) => field, | RequestField::Header(ref field, _)
RequestField::NewtypeBody(ref field) => field, | RequestField::NewtypeBody(ref field)
RequestField::Path(ref field) => field, | RequestField::Path(ref field)
RequestField::Query(ref field) => 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 { if self.kind() == kind {
Some(self.field()) Some(self.field())
} else { } else {
@ -411,11 +446,17 @@ impl RequestField {
} }
} }
/// The types of fields that a request can have, without their values.
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
enum RequestFieldKind { enum RequestFieldKind {
/// See the similarly named variant of `RequestField`.
Body, Body,
/// See the similarly named variant of `RequestField`.
Header, Header,
/// See the similarly named variant of `RequestField`.
NewtypeBody, NewtypeBody,
/// See the similarly named variant of `RequestField`.
Path, Path,
/// See the similarly named variant of `RequestField`.
Query, Query,
} }

View File

@ -1,30 +1,39 @@
//! Details of the `response` section of the procedural macro.
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta};
use crate::api::strip_serde_attrs; use crate::api::strip_serde_attrs;
/// The result of processing the `request` section of the macro.
pub struct Response { pub struct Response {
/// The fields of the response.
fields: Vec<ResponseField>, fields: Vec<ResponseField>,
} }
impl Response { impl Response {
/// Whether or not this response has any data in the HTTP body.
pub fn has_body_fields(&self) -> bool { pub fn has_body_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_body()) self.fields.iter().any(|field| field.is_body())
} }
/// Whether or not this response has any fields.
pub fn has_fields(&self) -> bool { pub fn has_fields(&self) -> bool {
!self.fields.is_empty() !self.fields.is_empty()
} }
/// Whether or not this response has any data in HTTP headers.
pub fn has_header_fields(&self) -> bool { pub fn has_header_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_header()) 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 { pub fn has_body(&self) -> bool {
self.fields.iter().any(|field| !field.is_header()) self.fields.iter().any(|field| !field.is_header())
} }
/// Produces code for a request struct initializer.
pub fn init_fields(&self) -> TokenStream { pub fn init_fields(&self) -> TokenStream {
let fields = self let fields = self
.fields .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 { pub fn apply_header_fields(&self) -> TokenStream {
let header_calls = self.fields.iter().filter_map(|response_field| { let header_calls = self.fields.iter().filter_map(|response_field| {
if let ResponseField::Header(ref field, ref header) = *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 { 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 let field_name = field
.ident .ident
.as_ref() .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> { pub fn newtype_body_field(&self) -> Option<&Field> {
for response_field in self.fields.iter() { for response_field in self.fields.iter() {
match *response_field { match *response_field {
@ -217,7 +229,7 @@ impl From<Vec<Field>> for Response {
} }
}).collect(); }).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 { pub enum ResponseField {
/// JSON data in the body of the response.
Body(Field), Body(Field),
/// Data in an HTTP header.
Header(Field, String), Header(Field, String),
/// A specific data type in the body of the response.
NewtypeBody(Field), NewtypeBody(Field),
} }
impl ResponseField { impl ResponseField {
/// Gets the inner `Field` value.
fn field(&self) -> &Field { fn field(&self) -> &Field {
match *self { match *self {
ResponseField::Body(ref field) => field, ResponseField::Body(ref field)
ResponseField::Header(ref field, _) => field, | ResponseField::Header(ref field, _)
ResponseField::NewtypeBody(ref field) => field, | ResponseField::NewtypeBody(ref field) => field,
} }
} }
/// Whether or not this response field is a body kind.
fn is_body(&self) -> bool { fn is_body(&self) -> bool {
match *self { match *self {
ResponseField::Body(..) => true, ResponseField::Body(_) => true,
_ => false, _ => false,
} }
} }
/// Whether or not this response field is a header kind.
fn is_header(&self) -> bool { fn is_header(&self) -> bool {
match *self { match *self {
ResponseField::Header(..) => true, ResponseField::Header(..) => true,
@ -321,8 +340,12 @@ impl ResponseField {
} }
} }
/// The types of fields that a response can have, without their values.
enum ResponseFieldKind { enum ResponseFieldKind {
/// See the similarly named variant of `ResponseField`.
Body, Body,
/// See the similarly named variant of `ResponseField`.
Header, Header,
/// See the similarly named variant of `ResponseField`.
NewtypeBody, NewtypeBody,
} }

View File

@ -3,7 +3,31 @@
//! //!
//! See the documentation for the `ruma_api!` macro for usage details. //! 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"] #![recursion_limit = "256"]
extern crate proc_macro; extern crate proc_macro;