Add rustfmt and clippy to CI and address clippy warnings.
This commit is contained in:
parent
a92e71f873
commit
6a09f1f754
@ -1 +0,0 @@
|
||||
merge_imports = true
|
@ -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:
|
||||
|
@ -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<Vec<FieldValue>> 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`"),
|
||||
|
@ -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<RawApi> 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<FieldValue>,
|
||||
/// The `request` section of the macro.
|
||||
pub request: Vec<Field>,
|
||||
/// The `response` section of the macro.
|
||||
pub response: Vec<Field>,
|
||||
}
|
||||
|
||||
@ -493,7 +506,7 @@ impl Parse for RawApi {
|
||||
let response;
|
||||
braced!(response in input);
|
||||
|
||||
Ok(RawApi {
|
||||
Ok(Self {
|
||||
metadata: metadata
|
||||
.parse_terminated::<FieldValue, Token![,]>(FieldValue::parse)?
|
||||
.into_iter()
|
||||
|
@ -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<RequestField>,
|
||||
}
|
||||
|
||||
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<Item = &RequestField> {
|
||||
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<Vec<Field>> 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<String>) -> RequestField {
|
||||
/// Creates a new `RequestField`.
|
||||
fn new(kind: RequestFieldKind, field: Field, header: Option<String>) -> 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,
|
||||
}
|
||||
|
@ -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<ResponseField>,
|
||||
}
|
||||
|
||||
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<Vec<Field>> 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,
|
||||
}
|
||||
|
26
src/lib.rs
26
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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user