mirror of https://github.com/rwf2/Rocket.git
Add a 'FromFormValue' derive. Start 'codegen_next' crate.
The 'codegen_next' crate will eventually be renamed 'codegen'. It contains procedural macros written with the upcoming 'proc_macro' APIs, which will eventually be stabilized. All compiler extensions in the present 'codegen' crate will be rewritten as procedural macros and moved to the 'codegen_next' crate. At present, macros from 'codegen_next' are exported from the core `rocket` crate automatically. In the future, we may wish to feature-gate this export to allow using Rocket's core without codegen. Resolves #16.
This commit is contained in:
parent
226990584b
commit
efc511c6dc
|
@ -5,6 +5,7 @@ codegen-units = 4
|
||||||
members = [
|
members = [
|
||||||
"lib/",
|
"lib/",
|
||||||
"codegen/",
|
"codegen/",
|
||||||
|
"codegen_next/",
|
||||||
"contrib/",
|
"contrib/",
|
||||||
"examples/cookies",
|
"examples/cookies",
|
||||||
"examples/errors",
|
"examples/errors",
|
||||||
|
|
|
@ -13,7 +13,7 @@ expressibility, and speed. Here's an example of a complete Rocket application:
|
||||||
#![feature(plugin, decl_macro)]
|
#![feature(plugin, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
#[get("/<name>/<age>")]
|
#[get("/<name>/<age>")]
|
||||||
fn hello(name: String, age: u8) -> String {
|
fn hello(name: String, age: u8) -> String {
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "rocket_codegen_next"
|
||||||
|
version = "0.4.0-dev"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
description = "Procedural macros for the Rocket web framework."
|
||||||
|
documentation = "https://api.rocket.rs/rocket_codegen/"
|
||||||
|
homepage = "https://rocket.rs"
|
||||||
|
repository = "https://github.com/SergioBenitez/Rocket"
|
||||||
|
readme = "../README.md"
|
||||||
|
keywords = ["rocket", "web", "framework", "code", "generation"]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "0.5.1"
|
||||||
|
proc-macro2 = { version = "0.3.6", features = ["nightly"] }
|
||||||
|
syn = { version = "0.13.1", features = ["full", "extra-traits"] }
|
|
@ -0,0 +1,101 @@
|
||||||
|
use syn::*;
|
||||||
|
use ext::*;
|
||||||
|
use quote::Tokens;
|
||||||
|
use spanned::Spanned;
|
||||||
|
|
||||||
|
use FieldMember;
|
||||||
|
|
||||||
|
pub trait CodegenFieldsExt {
|
||||||
|
fn surround(&self, tokens: Tokens) -> Tokens;
|
||||||
|
fn ignore_tokens(&self) -> Tokens;
|
||||||
|
fn id_match_tokens(&self) -> Tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_to_ident(i: usize, field: &Field) -> Ident {
|
||||||
|
let name = match field.ident {
|
||||||
|
Some(id) => format!("_{}", id),
|
||||||
|
None => format!("_{}", i)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ident::new(&name, field.span().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_to_match((i, field): (usize, &Field)) -> Tokens {
|
||||||
|
let ident = field_to_ident(i, field);
|
||||||
|
match field.ident {
|
||||||
|
Some(id) => quote!(#id: #ident),
|
||||||
|
None => quote!(#ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CodegenFieldsExt for Fields {
|
||||||
|
fn surround(&self, tokens: Tokens) -> Tokens {
|
||||||
|
match *self {
|
||||||
|
Fields::Named(..) => quote!({ #tokens }),
|
||||||
|
Fields::Unnamed(..) => quote!(( #tokens )),
|
||||||
|
Fields::Unit => quote!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ignore_tokens(&self) -> Tokens {
|
||||||
|
self.surround(quote!(..))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id_match_tokens(&self) -> Tokens {
|
||||||
|
let idents = self.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(field_to_match);
|
||||||
|
|
||||||
|
self.surround(quote!(#(#idents),*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TokensExt {
|
||||||
|
fn tokens(&self) -> Tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f> TokensExt for FieldMember<'f> {
|
||||||
|
fn tokens(&self) -> Tokens {
|
||||||
|
let index = self.member.unnamed().map(|i| i.index).unwrap_or(0);
|
||||||
|
let ident = field_to_ident(index as usize, &self.field);
|
||||||
|
quote!(#ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use rocket::http::{ContentType, MediaType, Status};
|
||||||
|
|
||||||
|
// impl TokensExt for ContentType {
|
||||||
|
// fn tokens(&self) -> Tokens {
|
||||||
|
// let mt_tokens = self.0.tokens();
|
||||||
|
// quote!(rocket::http::ContentType(#mt_tokens))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl TokensExt for MediaType {
|
||||||
|
// fn tokens(&self) -> Tokens {
|
||||||
|
// let (top, sub) = (self.top().as_str(), self.sub().as_str());
|
||||||
|
// let (keys, values) = (self.params().map(|(k, _)| k), self.params().map(|(_, v)| v));
|
||||||
|
// quote!(rocket::http::MediaType {
|
||||||
|
// source: rocket::http::Source::None,
|
||||||
|
// top: rocket::http::IndexedStr::Concrete(
|
||||||
|
// std::borrow::Cow::Borrowed(#top)
|
||||||
|
// ),
|
||||||
|
// sub: rocket::http::IndexedStr::Concrete(
|
||||||
|
// std::borrow::Cow::Borrowed(#sub)
|
||||||
|
// ),
|
||||||
|
// params: rocket::http::MediaParams::Static(&[
|
||||||
|
// #((
|
||||||
|
// rocket::http::IndexedStr::Concrete(std::borrow::Cow::Borrowed(#keys)),
|
||||||
|
// rocket::http::IndexedStr::Concrete(std::borrow::Cow::Borrowed(#values))
|
||||||
|
// )),*
|
||||||
|
// ])
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl TokensExt for Status {
|
||||||
|
// fn tokens(&self) -> Tokens {
|
||||||
|
// let (code, reason) = (self.code, self.reason);
|
||||||
|
// quote!(rocket::http::Status { code: #code, reason: #reason })
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -0,0 +1,169 @@
|
||||||
|
use syn::*;
|
||||||
|
use FieldMember;
|
||||||
|
use spanned::Spanned;
|
||||||
|
|
||||||
|
pub trait MemberExt {
|
||||||
|
fn named(&self) -> Option<&Ident>;
|
||||||
|
fn unnamed(&self) -> Option<&Index>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemberExt for Member {
|
||||||
|
fn named(&self) -> Option<&Ident> {
|
||||||
|
match *self {
|
||||||
|
Member::Named(ref named) => Some(named),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unnamed(&self) -> Option<&Index> {
|
||||||
|
match *self {
|
||||||
|
Member::Unnamed(ref unnamed) => Some(unnamed),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait FieldsExt {
|
||||||
|
fn len(&self) -> usize;
|
||||||
|
fn is_empty(&self) -> bool;
|
||||||
|
fn named(&self) -> Option<&FieldsNamed>;
|
||||||
|
fn is_named(&self) -> bool;
|
||||||
|
fn unnamed(&self) -> Option<&FieldsUnnamed>;
|
||||||
|
fn is_unnamed(&self) -> bool;
|
||||||
|
fn is_unit(&self) -> bool;
|
||||||
|
fn nth(&self, i: usize) -> Option<&Field>;
|
||||||
|
fn find_member(&self, member: &Member) -> Option<&Field>;
|
||||||
|
fn to_field_members<'f>(&'f self) -> Box<Iterator<Item = FieldMember<'f>> + 'f>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldsExt for Fields {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Fields::Named(ref fields) => fields.named.len(),
|
||||||
|
Fields::Unnamed(ref fields) => fields.unnamed.len(),
|
||||||
|
Fields::Unit => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named(&self) -> Option<&FieldsNamed> {
|
||||||
|
match *self {
|
||||||
|
Fields::Named(ref named) => Some(named),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_named(&self) -> bool {
|
||||||
|
self.named().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unnamed(&self) -> Option<&FieldsUnnamed> {
|
||||||
|
match *self {
|
||||||
|
Fields::Unnamed(ref unnamed) => Some(unnamed),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_unnamed(&self) -> bool {
|
||||||
|
self.unnamed().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_unit(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Fields::Unit => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_field_members<'f>(&'f self) -> Box<Iterator<Item = FieldMember<'f>> + 'f> {
|
||||||
|
Box::new(self.iter().enumerate().map(|(index, field)| {
|
||||||
|
if let Some(ident) = field.ident {
|
||||||
|
FieldMember { field, member: Member::Named(ident) }
|
||||||
|
} else {
|
||||||
|
let index = Index { index: index as u32, span: field.span().into() };
|
||||||
|
let member = Member::Unnamed(index);
|
||||||
|
FieldMember { field, member }
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&self, i: usize) -> Option<&Field> {
|
||||||
|
match *self {
|
||||||
|
Fields::Named(ref fields) => fields.named.iter().nth(i),
|
||||||
|
Fields::Unnamed(ref fields) => fields.unnamed.iter().nth(i),
|
||||||
|
Fields::Unit => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_member(&self, member: &Member) -> Option<&Field> {
|
||||||
|
if let (Some(fields), Some(ident)) = (self.named(), member.named()) {
|
||||||
|
fields.named.iter().find(|f| f.ident.as_ref().unwrap() == ident)
|
||||||
|
} else if let (Some(fields), Some(member)) = (self.unnamed(), member.unnamed()) {
|
||||||
|
fields.unnamed.iter().nth(member.index as usize)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PathExt {
|
||||||
|
fn is(&self, global: bool, segments: &[&str]) -> bool;
|
||||||
|
fn is_local(&self, segments: &[&str]) -> bool;
|
||||||
|
fn is_global(&self, segments: &[&str]) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathExt for Path {
|
||||||
|
fn is(&self, global: bool, segments: &[&str]) -> bool {
|
||||||
|
if self.global() != global || self.segments.len() != segments.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (segment, wanted) in self.segments.iter().zip(segments.iter()) {
|
||||||
|
if segment.ident != wanted {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_local(&self, segments: &[&str]) -> bool {
|
||||||
|
self.is(false, segments)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_global(&self, segments: &[&str]) -> bool {
|
||||||
|
self.is(true, segments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DataExt {
|
||||||
|
fn into_enum(self) -> Option<DataEnum>;
|
||||||
|
fn into_struct(self) -> Option<DataStruct>;
|
||||||
|
fn into_union(self) -> Option<DataUnion>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataExt for Data {
|
||||||
|
fn into_enum(self) -> Option<DataEnum> {
|
||||||
|
match self {
|
||||||
|
Data::Enum(e) => Some(e),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_struct(self) -> Option<DataStruct> {
|
||||||
|
match self {
|
||||||
|
Data::Struct(s) => Some(s),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_union(self) -> Option<DataUnion> {
|
||||||
|
match self {
|
||||||
|
Data::Union(u) => Some(u),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
#![feature(proc_macro, core_intrinsics, decl_macro)]
|
||||||
|
#![recursion_limit="256"]
|
||||||
|
|
||||||
|
extern crate syn;
|
||||||
|
extern crate proc_macro;
|
||||||
|
extern crate proc_macro2;
|
||||||
|
#[macro_use] extern crate quote;
|
||||||
|
|
||||||
|
mod parser;
|
||||||
|
mod spanned;
|
||||||
|
mod ext;
|
||||||
|
mod codegen_ext;
|
||||||
|
|
||||||
|
use parser::Result as PResult;
|
||||||
|
use proc_macro::{Span, TokenStream};
|
||||||
|
use spanned::Spanned;
|
||||||
|
|
||||||
|
use ext::*;
|
||||||
|
use syn::*;
|
||||||
|
|
||||||
|
const NO_FIELDS_ERR: &str = "variants in `FromFormValue` derives cannot have fields";
|
||||||
|
const NO_GENERICS: &str = "enums with generics cannot derive `FromFormValue`";
|
||||||
|
const ONLY_ENUMS: &str = "`FromFormValue` can only be derived for enums";
|
||||||
|
const EMPTY_ENUM_WARN: &str = "deriving `FromFormValue` for empty enum";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct FieldMember<'f> {
|
||||||
|
field: &'f Field,
|
||||||
|
member: Member
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_input(input: DeriveInput) -> PResult<DataEnum> {
|
||||||
|
// This derive doesn't support generics. Error out if there are generics.
|
||||||
|
if !input.generics.params.is_empty() {
|
||||||
|
return Err(input.generics.span().error(NO_GENERICS));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This derive only works for enums. Error out if the input is not an enum.
|
||||||
|
let input_span = input.span();
|
||||||
|
let data = input.data.into_enum().ok_or_else(|| input_span.error(ONLY_ENUMS))?;
|
||||||
|
|
||||||
|
// This derive only works for variants that are nullary.
|
||||||
|
for variant in data.variants.iter() {
|
||||||
|
if !variant.fields.is_empty() {
|
||||||
|
return Err(variant.span().error(NO_FIELDS_ERR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit a warning if the enum is empty.
|
||||||
|
if data.variants.is_empty() {
|
||||||
|
Span::call_site().warning(EMPTY_ENUM_WARN).emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_derive_from_form_value(input: TokenStream) -> PResult<TokenStream> {
|
||||||
|
// Parse the input `TokenStream` as a `syn::DeriveInput`, an AST.
|
||||||
|
let input: DeriveInput = syn::parse(input).map_err(|e| {
|
||||||
|
Span::call_site().error(format!("error: failed to parse input: {:?}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Validate the enum.
|
||||||
|
let name = input.ident;
|
||||||
|
let enum_data = validate_input(input)?;
|
||||||
|
|
||||||
|
// Create iterators over the identifers as idents and as strings.
|
||||||
|
let variant_strs = enum_data.variants.iter().map(|v| v.ident.as_ref() as &str);
|
||||||
|
let variant_idents = enum_data.variants.iter().map(|v| v.ident);
|
||||||
|
let names = ::std::iter::repeat(name);
|
||||||
|
|
||||||
|
// Generate the implementation.
|
||||||
|
Ok(quote! {
|
||||||
|
mod scope {
|
||||||
|
extern crate std;
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use self::std::prelude::v1::*;
|
||||||
|
use self::rocket::request::FromFormValue;
|
||||||
|
use self::rocket::http::RawStr;
|
||||||
|
|
||||||
|
impl<'v> FromFormValue<'v> for #name {
|
||||||
|
type Error = &'v RawStr;
|
||||||
|
|
||||||
|
fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
|
||||||
|
#(if v.as_uncased_str() == #variant_strs {
|
||||||
|
return Ok(#names::#variant_idents);
|
||||||
|
})*
|
||||||
|
|
||||||
|
Err(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(FromFormValue)]
|
||||||
|
pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
|
||||||
|
real_derive_from_form_value(input).unwrap_or_else(|diag| {
|
||||||
|
diag.emit();
|
||||||
|
TokenStream::empty()
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use syn::token;
|
||||||
|
use syn::synom::Synom;
|
||||||
|
use syn::buffer::{Cursor, TokenBuffer};
|
||||||
|
|
||||||
|
use proc_macro::{TokenStream, Span, Diagnostic};
|
||||||
|
|
||||||
|
pub use proc_macro2::Delimiter;
|
||||||
|
|
||||||
|
pub type Result<T> = ::std::result::Result<T, Diagnostic>;
|
||||||
|
|
||||||
|
pub enum Seperator {
|
||||||
|
Comma,
|
||||||
|
Pipe,
|
||||||
|
Semi,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Parser {
|
||||||
|
buffer: Box<TokenBuffer>,
|
||||||
|
cursor: Cursor<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
pub fn new(tokens: TokenStream) -> Parser {
|
||||||
|
let buffer = Box::new(TokenBuffer::new(tokens.into()));
|
||||||
|
let cursor = unsafe {
|
||||||
|
let buffer: &'static TokenBuffer = ::std::mem::transmute(&*buffer);
|
||||||
|
buffer.begin()
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser {
|
||||||
|
buffer: buffer,
|
||||||
|
cursor: cursor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_span(&self) -> Span {
|
||||||
|
self.cursor.token_tree()
|
||||||
|
.map(|_| self.cursor.span().unstable())
|
||||||
|
.unwrap_or_else(|| Span::call_site())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse<T: Synom>(&mut self) -> Result<T> {
|
||||||
|
let (val, cursor) = T::parse(self.cursor)
|
||||||
|
.map_err(|e| {
|
||||||
|
let expected = match T::description() {
|
||||||
|
Some(desc) => desc,
|
||||||
|
None => unsafe { ::std::intrinsics::type_name::<T>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
self.current_span().error(format!("{}: expected {}", e, expected))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.cursor = cursor;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eat<T: Synom>(&mut self) -> bool {
|
||||||
|
self.parse::<T>().is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_group<F, T>(&mut self, delim: Delimiter, f: F) -> Result<T>
|
||||||
|
where F: FnOnce(&mut Parser) -> Result<T>
|
||||||
|
{
|
||||||
|
if let Some((group_cursor, _, next_cursor)) = self.cursor.group(delim) {
|
||||||
|
self.cursor = group_cursor;
|
||||||
|
let result = f(self);
|
||||||
|
self.cursor = next_cursor;
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
let expected = match delim {
|
||||||
|
Delimiter::Brace => "curly braced group",
|
||||||
|
Delimiter::Bracket => "square bracketed group",
|
||||||
|
Delimiter::Parenthesis => "parenthesized group",
|
||||||
|
Delimiter::None => "invisible group"
|
||||||
|
};
|
||||||
|
|
||||||
|
Err(self.current_span()
|
||||||
|
.error(format!("parse error: expected {}", expected)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_sep<F, T>(&mut self, sep: Seperator, mut f: F) -> Result<Vec<T>>
|
||||||
|
where F: FnMut(&mut Parser) -> Result<T>
|
||||||
|
{
|
||||||
|
let mut output = vec![];
|
||||||
|
while !self.is_eof() {
|
||||||
|
output.push(f(self)?);
|
||||||
|
let have_sep = match sep {
|
||||||
|
Seperator::Comma => self.eat::<token::Comma>(),
|
||||||
|
Seperator::Pipe => self.eat::<token::Or>(),
|
||||||
|
Seperator::Semi => self.eat::<token::Semi>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !have_sep {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eof(&self) -> Result<()> {
|
||||||
|
if !self.cursor.eof() {
|
||||||
|
let diag = self.current_span()
|
||||||
|
.error("trailing characters; expected eof");
|
||||||
|
|
||||||
|
return Err(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_eof(&self) -> bool {
|
||||||
|
self.eof().is_ok()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
use proc_macro::Span;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{Tokens, ToTokens};
|
||||||
|
|
||||||
|
pub trait Spanned {
|
||||||
|
fn span(&self) -> Span;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Remove this once proc_macro's stabilize.
|
||||||
|
impl<T: ToTokens> Spanned for T {
|
||||||
|
fn span(&self) -> Span {
|
||||||
|
let mut tokens = Tokens::new();
|
||||||
|
self.to_tokens(&mut tokens);
|
||||||
|
let token_stream = TokenStream::from(tokens);
|
||||||
|
let mut iter = token_stream.into_iter();
|
||||||
|
let mut span = match iter.next() {
|
||||||
|
Some(tt) => tt.span().unstable(),
|
||||||
|
None => {
|
||||||
|
return Span::call_site();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for tt in iter {
|
||||||
|
if let Some(joined) = span.join(tt.span().unstable()) {
|
||||||
|
span = joined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,22 @@
|
||||||
#![feature(plugin, decl_macro, custom_derive)]
|
#![feature(plugin, decl_macro, custom_derive)]
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use rocket::request::{Form, FromFormValue};
|
use rocket::request::Form;
|
||||||
use rocket::response::NamedFile;
|
use rocket::response::NamedFile;
|
||||||
use rocket::http::RawStr;
|
use rocket::http::RawStr;
|
||||||
|
|
||||||
#[cfg(test)] mod tests;
|
#[cfg(test)] mod tests;
|
||||||
|
|
||||||
// TODO: Make deriving `FromForm` for this enum possible.
|
// TODO: Make deriving `FromForm` for this enum possible.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, FromFormValue)]
|
||||||
enum FormOption {
|
enum FormOption {
|
||||||
A, B, C
|
A, B, C
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'v> FromFormValue<'v> for FormOption {
|
|
||||||
type Error = &'v RawStr;
|
|
||||||
|
|
||||||
fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
|
|
||||||
let variant = match v.as_str() {
|
|
||||||
"a" => FormOption::A,
|
|
||||||
"b" => FormOption::B,
|
|
||||||
"c" => FormOption::C,
|
|
||||||
_ => return Err(v)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(variant)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, FromForm)]
|
#[derive(Debug, FromForm)]
|
||||||
struct FormInput<'r> {
|
struct FormInput<'r> {
|
||||||
checkbox: bool,
|
checkbox: bool,
|
||||||
|
|
|
@ -131,7 +131,7 @@ fn check_semantically_invalid_forms() {
|
||||||
form_vals[1] = "NaN";
|
form_vals[1] = "NaN";
|
||||||
assert_invalid_form(&client, &mut form_vals);
|
assert_invalid_form(&client, &mut form_vals);
|
||||||
|
|
||||||
form_vals[2] = "A";
|
form_vals[2] = "A?";
|
||||||
assert_invalid_form(&client, &mut form_vals);
|
assert_invalid_form(&client, &mut form_vals);
|
||||||
form_vals[2] = " B";
|
form_vals[2] = " B";
|
||||||
assert_invalid_form(&client, &mut form_vals);
|
assert_invalid_form(&client, &mut form_vals);
|
||||||
|
@ -143,7 +143,7 @@ fn check_semantically_invalid_forms() {
|
||||||
assert_invalid_form(&client, &mut form_vals);
|
assert_invalid_form(&client, &mut form_vals);
|
||||||
|
|
||||||
// password and textarea are always valid, so we skip them
|
// password and textarea are always valid, so we skip them
|
||||||
form_vals[5] = "A";
|
form_vals[5] = "A.";
|
||||||
assert_invalid_form(&client, &mut form_vals);
|
assert_invalid_form(&client, &mut form_vals);
|
||||||
form_vals[5] = "b ";
|
form_vals[5] = "b ";
|
||||||
assert_invalid_form(&client, &mut form_vals);
|
assert_invalid_form(&client, &mut form_vals);
|
||||||
|
|
|
@ -18,6 +18,7 @@ categories = ["web-programming::http-server"]
|
||||||
tls = ["rustls", "hyper-sync-rustls"]
|
tls = ["rustls", "hyper-sync-rustls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rocket_codegen_next = { version = "0.4.0-dev", path = "../codegen_next" }
|
||||||
yansi = "0.4"
|
yansi = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
percent-encoding = "1"
|
percent-encoding = "1"
|
||||||
|
@ -32,10 +33,14 @@ pear = { git = "http://github.com/SergioBenitez/pear" }
|
||||||
pear_codegen = "0.0"
|
pear_codegen = "0.0"
|
||||||
rustls = { version = "0.12.0", optional = true }
|
rustls = { version = "0.12.0", optional = true }
|
||||||
hyper = { version = "0.10.13", default-features = false }
|
hyper = { version = "0.10.13", default-features = false }
|
||||||
hyper-sync-rustls = { version = "=0.3.0-rc.2", features = ["server"], optional = true }
|
|
||||||
indexmap = "1.0"
|
indexmap = "1.0"
|
||||||
isatty = "0.1"
|
isatty = "0.1"
|
||||||
|
|
||||||
|
[dependencies.hyper-sync-rustls]
|
||||||
|
version = "=0.3.0-rc.2"
|
||||||
|
features = ["server"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dependencies.cookie]
|
[dependencies.cookie]
|
||||||
git = "https://github.com/alexcrichton/cookie-rs"
|
git = "https://github.com/alexcrichton/cookie-rs"
|
||||||
rev = "8579b4b"
|
rev = "8579b4b"
|
||||||
|
|
|
@ -96,6 +96,9 @@
|
||||||
//! module documentation](/rocket/local) and the [testing chapter of the
|
//! module documentation](/rocket/local) and the [testing chapter of the
|
||||||
//! guide](https://rocket.rs/guide/testing/#testing) include detailed examples.
|
//! guide](https://rocket.rs/guide/testing/#testing) include detailed examples.
|
||||||
|
|
||||||
|
#[allow(unused_imports)] #[macro_use] extern crate rocket_codegen_next;
|
||||||
|
#[doc(hidden)] pub use rocket_codegen_next::*;
|
||||||
|
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
#[macro_use] extern crate pear;
|
#[macro_use] extern crate pear;
|
||||||
#[cfg(feature = "tls")] extern crate rustls;
|
#[cfg(feature = "tls")] extern crate rustls;
|
||||||
|
|
Loading…
Reference in New Issue