<commit_before>//! oppgave - A simple Redis-based task queue
//!
//!
//! ## Example: Producer
//!
//! ```rust,ignore
//! #[derive(RustcDecodable, RustcEncodable)]
//! struct Job { id: u64 }
//!
//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! let con = client.get_connection().unwrap();
//! let producer = Queue::new("default".into(), con);
//!
//! producer.push(Job{ id: 42 });
//! ```
//!
//! ## Example: Worker
//!
//! ```rust,ignore
//! #[derive(RustcDecodable, RustcEncodable)]
//! struct Job { id: u64 }
//!
//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! let con = client.get_connection().unwrap();
//! let worker = Queue::new("default".into(), con);
//!
//! while let Some(task) = worker.next() {
//!     println!("Working with Job {}", job.id);
//! }
//! ```

#![deny(missing_docs)]

extern crate rustc_serialize;
extern crate redis;
extern crate libc;

use std::{str, thread};
use std::cell::Cell;
use std::ops::{Deref, Drop};
use std::convert::From;
use rustc_serialize::{json, Decodable, Encodable};
use redis::{Value, RedisResult, ErrorKind, Commands};

/// Return the PID of the calling process.
/// TODO: Does this work on Windows?
fn getpid() -> i32 {
    unsafe { libc::getpid() as i32 }
}

/// Task objects that can be reconstructed from the JSON stored in Redis
///
/// Implemented for all `Decodable` objects by default.
pub trait TaskDecodable where Self: Sized {
    /// Decode the given Redis value into a task
    ///
    /// This should decode the string value into a proper task.
    /// The string value is encoded as JSON.
    fn decode_task(value: &Value) -> RedisResult<Self>;
}

/// Task objects that can be encoded to JSON to be stored in Redis
///
/// Implemented for all `Encodable` objects by default.
pub trait TaskEncodable {
    /// Encode the value into a Blob to insert into Redis
    ///
    /// It should encode the value into a JSON string.
    fn encode_task(&self) -> Vec<u8>;
}

impl<T: Decodable> TaskDecodable for T {
    fn decode_task(value: &Value) -> RedisResult<T> {
        match value {
            &Value::Data(ref v) => {
                let s = try!(str::from_utf8(&v));
                Ok(try!(json::decode(&s).map_err(|_| (ErrorKind::TypeError, "JSON decode failed"))))

            },
            v @ _ => {
                println!("what do we have here: {:?}", v);
                try!(Err((ErrorKind::TypeError, "Can only decode from a string")))
            }
        }
    }
}

impl<T: Encodable> TaskEncodable for T {
    fn encode_task(&self) -> Vec<u8> {
        json::encode(self).unwrap().into_bytes()
    }
}

/// A wrapper of the fetched task
///
/// Pops a task from the backup on drop.
pub struct TaskGuard<'a, T: 'a> {
    task: T,
    queue: &'a Queue,
    failed: Cell<bool>,
}

impl<'a, T> TaskGuard<'a, T> {
    /// Stop the underlying queue
    pub fn stop(&self) {
        self.queue.stop();
    }

    /// Fail the current task and don't remove it from the backup queue
    pub fn fail(&self) {
        self.failed.set(true);
    }

    /// Get access to the underlying task
    pub fn inner(&self) -> &T {
        &self.task
    }

    /// Get access to the wrapper queue
    pub fn queue(&self) -> &Queue {
        self.queue
    }
}

impl<'a, T> Deref for TaskGuard<'a, T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.task
    }
}

impl<'a, T> Drop for TaskGuard<'a, T> {
    fn drop(&mut self) {
        if !self.failed.get() {
            // Pop job from backup queue
            let backup = &self.queue.backup_queue[..];
            let _ : () = self.queue.client.lpop(backup).expect("LPOP from backup queue failed");
        }
    }
}

/// A Queue allows to push new tasks or wait for them
///
/// Allows for reliable job processing.
///
/// On fetch jobs are moved to a backup queue.
/// They are popped from the backup queue, when the returned task guard is dropped.
///
/// ## Example
///
/// ```rust,ignore
/// #[derive(RustcDecodable, RustcEncodable)]
/// struct Job { id: u64 }
///
/// let client = redis::Client::open("redis://127.0.0.1/").unwrap();
/// let con = client.get_connection().unwrap();
/// let producer = Queue::new("default".into(), con);
///
/// producer.push(Job{ id: 42 });
/// ```
pub struct Queue {
    queue_name: String,
    backup_queue: String,
    stopped: Cell<bool>,
    client: redis::Connection,
}

impl Queue {
    /// Create a new Queue for the given name
    pub fn new(name: String, client: redis::Connection) -> Queue {
        let qname = format!("oppgave:{}", name);
        let backup_queue = format!("{}:{}:{}",
                                   qname,
                                   getpid(),
                                   thread::current().name().unwrap_or("default".into()));

        Queue {
            queue_name: qname,
            backup_queue: backup_queue,
            client: client,
            stopped: Cell::new(false),
        }
    }

    /// Stop processing the queue
    ///
    /// On the next `.next()` call `None` will be returned.
    pub fn stop(&self) {
        self.stopped.set(true);
    }

    /// Check if queue processing is stopped
    pub fn is_stopped(&self) -> bool {
        self.stopped.get()
    }

    /// Get the full queue name
    pub fn queue(&self) -> &str {
        &self.queue_name
    }

    /// Get the full backup queue name
    pub fn backup_queue(&self) -> &str {
        &self.backup_queue
    }

    /// Get the number of remaining tasks in the queue
    pub fn size(&self) -> u64 {
        self.client.llen(self.queue()).unwrap_or(0)
    }

    /// Push a new task to the queue
    pub fn push<T: TaskEncodable>(&self, task: T) {
        let _ : () = self.client.lpush(self.queue(), task.encode_task()).expect("LPUSH failed to enqueue task");
    }

    /// Grab the next task from the queue
    ///
    /// This method blocks and waits until a new task is available.
    pub fn next<T: TaskDecodable>(&self) -> Option<RedisResult<TaskGuard<T>>> {
        if self.stopped.get() {
            return None;
        }

        let v;
        {
            let qname = &self.queue_name[..];
            let backup = &self.backup_queue[..];

            v = match self.client.brpoplpush(qname, backup, 0) {
                Ok(v) => v,
                Err(_) => {
                    return Some(Err(From::from((ErrorKind::TypeError, "next failed"))));
                }
            };
        }

        let v = match v {
            v @ Value::Data(_) => v,
            _ => {
                return Some(Err(From::from((ErrorKind::TypeError, "Not a proper reply"))));
            }
        };

        let task = T::decode_task(&v).unwrap();

        Some(Ok(TaskGuard{task: task, queue: self, failed: Cell::new(false)}))
    }
}

#[cfg(test)]
mod test {
    extern crate redis;

    use redis::Commands;
    use super::{Queue, TaskGuard};

    #[derive(RustcDecodable, RustcEncodable)]
    struct Job {
        id: u64
    }

    #[test]
    fn decodes_job() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("default".into(), con2);

        let _ : () = con.rpush(worker.queue(), "{\"id\":42}").unwrap();

        let j = worker.next::<Job>().unwrap().unwrap();
        assert_eq!(42, j.id);
    }

    #[test]
    fn releases_job() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("default".into(), con2);
        let bqueue = worker.backup_queue();

        let _ : () = con.del(bqueue).unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":42}").unwrap();

        {
            let j = worker.next::<Job>().unwrap().unwrap();
            assert_eq!(42, j.id);
            let in_backup : Vec<String> = con.lrange(bqueue, 0, -1).unwrap();
            assert_eq!(1, in_backup.len());
            assert_eq!("{\"id\":42}", in_backup[0]);
        }

        let in_backup : u32 = con.llen(bqueue).unwrap();
        assert_eq!(0, in_backup);
    }

    #[test]
    fn can_be_stopped() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("stopper".into(), con2);

        let _ : () = con.del(worker.queue()).unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":1}").unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":2}").unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":3}").unwrap();

        assert_eq!(3, worker.size());

        while let Some(task) = worker.next::<Job>() {
            let task = task.unwrap();
            task.stop();
        }

        assert_eq!(2, worker.size());
    }

    #[test]
    fn can_enqueue() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();

        let worker = Queue::new("enqueue".into(), con2);
        let _ : () = con.del(worker.queue()).unwrap();

        assert_eq!(0, worker.size());

        worker.push(Job{id: 53});

        assert_eq!(1, worker.size());

        let j = worker.next::<Job>().unwrap().unwrap();
        assert_eq!(53, j.id);
    }

    #[test]
    fn does_not_drop_failed() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("failure".into(), con2);

        let _ : () = con.del(worker.queue()).unwrap();
        let _ : () = con.del(worker.backup_queue()).unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":1}").unwrap();

        {
            let task : TaskGuard<Job> = worker.next().unwrap().unwrap();
            task.fail();
        }

        let len : u32 = con.llen(worker.backup_queue()).unwrap();
        assert_eq!(1, len);
    }
}
<commit_msg>fix: Handle decoding failure for task<commit_after>//! oppgave - A simple Redis-based task queue
//!
//!
//! ## Example: Producer
//!
//! ```rust,ignore
//! #[derive(RustcDecodable, RustcEncodable)]
//! struct Job { id: u64 }
//!
//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! let con = client.get_connection().unwrap();
//! let producer = Queue::new("default".into(), con);
//!
//! producer.push(Job{ id: 42 });
//! ```
//!
//! ## Example: Worker
//!
//! ```rust,ignore
//! #[derive(RustcDecodable, RustcEncodable)]
//! struct Job { id: u64 }
//!
//! let client = redis::Client::open("redis://127.0.0.1/").unwrap();
//! let con = client.get_connection().unwrap();
//! let worker = Queue::new("default".into(), con);
//!
//! while let Some(task) = worker.next() {
//!     println!("Working with Job {}", job.id);
//! }
//! ```

#![deny(missing_docs)]

extern crate rustc_serialize;
extern crate redis;
extern crate libc;

use std::{str, thread};
use std::cell::Cell;
use std::ops::{Deref, Drop};
use std::convert::From;
use rustc_serialize::{json, Decodable, Encodable};
use redis::{Value, RedisResult, ErrorKind, Commands};

/// Return the PID of the calling process.
/// TODO: Does this work on Windows?
fn getpid() -> i32 {
    unsafe { libc::getpid() as i32 }
}

/// Task objects that can be reconstructed from the JSON stored in Redis
///
/// Implemented for all `Decodable` objects by default.
pub trait TaskDecodable where Self: Sized {
    /// Decode the given Redis value into a task
    ///
    /// This should decode the string value into a proper task.
    /// The string value is encoded as JSON.
    fn decode_task(value: &Value) -> RedisResult<Self>;
}

/// Task objects that can be encoded to JSON to be stored in Redis
///
/// Implemented for all `Encodable` objects by default.
pub trait TaskEncodable {
    /// Encode the value into a Blob to insert into Redis
    ///
    /// It should encode the value into a JSON string.
    fn encode_task(&self) -> Vec<u8>;
}

impl<T: Decodable> TaskDecodable for T {
    fn decode_task(value: &Value) -> RedisResult<T> {
        match *value {
            Value::Data(ref v) => {
                let s = try!(str::from_utf8(&v));
                json::decode(&s).map_err(|_| From::from((ErrorKind::TypeError, "JSON decode failed")))

            },
            _ => {
                try!(Err((ErrorKind::TypeError, "Can only decode from a string")))
            }
        }
    }
}

impl<T: Encodable> TaskEncodable for T {
    fn encode_task(&self) -> Vec<u8> {
        json::encode(self).unwrap().into_bytes()
    }
}

/// A wrapper of the fetched task
///
/// Pops a task from the backup on drop.
pub struct TaskGuard<'a, T: 'a> {
    task: T,
    queue: &'a Queue,
    failed: Cell<bool>,
}

impl<'a, T> TaskGuard<'a, T> {
    /// Stop the underlying queue
    pub fn stop(&self) {
        self.queue.stop();
    }

    /// Fail the current task and don't remove it from the backup queue
    pub fn fail(&self) {
        self.failed.set(true);
    }

    /// Get access to the underlying task
    pub fn inner(&self) -> &T {
        &self.task
    }

    /// Get access to the wrapper queue
    pub fn queue(&self) -> &Queue {
        self.queue
    }
}

impl<'a, T> Deref for TaskGuard<'a, T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.task
    }
}

impl<'a, T> Drop for TaskGuard<'a, T> {
    fn drop(&mut self) {
        if !self.failed.get() {
            // Pop job from backup queue
            let backup = &self.queue.backup_queue[..];
            let _ : () = self.queue.client.lpop(backup).expect("LPOP from backup queue failed");
        }
    }
}

/// A Queue allows to push new tasks or wait for them
///
/// Allows for reliable job processing.
///
/// On fetch jobs are moved to a backup queue.
/// They are popped from the backup queue, when the returned task guard is dropped.
///
/// ## Example
///
/// ```rust,ignore
/// #[derive(RustcDecodable, RustcEncodable)]
/// struct Job { id: u64 }
///
/// let client = redis::Client::open("redis://127.0.0.1/").unwrap();
/// let con = client.get_connection().unwrap();
/// let producer = Queue::new("default".into(), con);
///
/// producer.push(Job{ id: 42 });
/// ```
pub struct Queue {
    queue_name: String,
    backup_queue: String,
    stopped: Cell<bool>,
    client: redis::Connection,
}

impl Queue {
    /// Create a new Queue for the given name
    pub fn new(name: String, client: redis::Connection) -> Queue {
        let qname = format!("oppgave:{}", name);
        let backup_queue = format!("{}:{}:{}",
                                   qname,
                                   getpid(),
                                   thread::current().name().unwrap_or("default".into()));

        Queue {
            queue_name: qname,
            backup_queue: backup_queue,
            client: client,
            stopped: Cell::new(false),
        }
    }

    /// Stop processing the queue
    ///
    /// On the next `.next()` call `None` will be returned.
    pub fn stop(&self) {
        self.stopped.set(true);
    }

    /// Check if queue processing is stopped
    pub fn is_stopped(&self) -> bool {
        self.stopped.get()
    }

    /// Get the full queue name
    pub fn queue(&self) -> &str {
        &self.queue_name
    }

    /// Get the full backup queue name
    pub fn backup_queue(&self) -> &str {
        &self.backup_queue
    }

    /// Get the number of remaining tasks in the queue
    pub fn size(&self) -> u64 {
        self.client.llen(self.queue()).unwrap_or(0)
    }

    /// Push a new task to the queue
    pub fn push<T: TaskEncodable>(&self, task: T) {
        let _ : () = self.client.lpush(self.queue(), task.encode_task()).expect("LPUSH failed to enqueue task");
    }

    /// Grab the next task from the queue
    ///
    /// This method blocks and waits until a new task is available.
    pub fn next<T: TaskDecodable>(&self) -> Option<RedisResult<TaskGuard<T>>> {
        if self.stopped.get() {
            return None;
        }

        let v;
        {
            let qname = &self.queue_name[..];
            let backup = &self.backup_queue[..];

            v = match self.client.brpoplpush(qname, backup, 0) {
                Ok(v) => v,
                Err(_) => {
                    return Some(Err(From::from((ErrorKind::TypeError, "next failed"))));
                }
            };
        }

        let v = match v {
            v @ Value::Data(_) => v,
            _ => {
                return Some(Err(From::from((ErrorKind::TypeError, "Not a proper reply"))));
            }
        };

        match T::decode_task(&v) {
            Err(e) => Some(Err(e)),
            Ok(task) => Some(Ok(TaskGuard{task: task, queue: self, failed: Cell::new(false)}))
        }
    }
}

#[cfg(test)]
mod test {
    extern crate redis;

    use redis::Commands;
    use super::{Queue, TaskGuard};

    #[derive(RustcDecodable, RustcEncodable)]
    struct Job {
        id: u64
    }

    #[test]
    fn decodes_job() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("default".into(), con2);

        let _ : () = con.rpush(worker.queue(), "{\"id\":42}").unwrap();

        let j = worker.next::<Job>().unwrap().unwrap();
        assert_eq!(42, j.id);
    }

    #[test]
    fn releases_job() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("default".into(), con2);
        let bqueue = worker.backup_queue();

        let _ : () = con.del(bqueue).unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":42}").unwrap();

        {
            let j = worker.next::<Job>().unwrap().unwrap();
            assert_eq!(42, j.id);
            let in_backup : Vec<String> = con.lrange(bqueue, 0, -1).unwrap();
            assert_eq!(1, in_backup.len());
            assert_eq!("{\"id\":42}", in_backup[0]);
        }

        let in_backup : u32 = con.llen(bqueue).unwrap();
        assert_eq!(0, in_backup);
    }

    #[test]
    fn can_be_stopped() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("stopper".into(), con2);

        let _ : () = con.del(worker.queue()).unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":1}").unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":2}").unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":3}").unwrap();

        assert_eq!(3, worker.size());

        while let Some(task) = worker.next::<Job>() {
            let task = task.unwrap();
            task.stop();
        }

        assert_eq!(2, worker.size());
    }

    #[test]
    fn can_enqueue() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();

        let worker = Queue::new("enqueue".into(), con2);
        let _ : () = con.del(worker.queue()).unwrap();

        assert_eq!(0, worker.size());

        worker.push(Job{id: 53});

        assert_eq!(1, worker.size());

        let j = worker.next::<Job>().unwrap().unwrap();
        assert_eq!(53, j.id);
    }

    #[test]
    fn does_not_drop_failed() {
        let client = redis::Client::open("redis://127.0.0.1:6380/").unwrap();
        let con = client.get_connection().unwrap();
        let con2 = client.get_connection().unwrap();
        let worker = Queue::new("failure".into(), con2);

        let _ : () = con.del(worker.queue()).unwrap();
        let _ : () = con.del(worker.backup_queue()).unwrap();
        let _ : () = con.lpush(worker.queue(), "{\"id\":1}").unwrap();

        {
            let task : TaskGuard<Job> = worker.next().unwrap().unwrap();
            task.fail();
        }

        let len : u32 = con.llen(worker.backup_queue()).unwrap();
        assert_eq!(1, len);
    }
}
<|endoftext|>


<commit_before>//! # Mime
//!
//! Mime is now Media Type, technically, but `Mime` is more immediately
//! understandable, so the main type here is `Mime`.
//!
//! ## What is Mime?
//!
//! Example mime string: `text/plain;charset=utf-8`
//!
//! ```rust
//! # #![allow(unstable)]
//! # use mime::Mime;
//! # use mime::TopLevel::Text;
//! # use mime::SubLevel::Plain;
//! # use mime::Attr::Charset;
//! # use mime::Value::Utf8;
//! let mime: Mime = "text/plain;charset=utf-8".parse().unwrap();
//! assert_eq!(mime, Mime(Text, Plain, vec![(Charset, Utf8)]));
//! ```

#![doc(html_root_url = "http://hyperium.github.io/mime.rs")]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(test, feature(test))]
#![feature(core, std_misc)]

#[macro_use]
extern crate log;

#[cfg(test)]
extern crate test;

use std::ascii::AsciiExt;
use std::fmt;
use std::str::FromStr;
use std::iter::Enumerate;
use std::str::Chars;

macro_rules! inspect(
    ($s:expr, $t:expr) => ({
        let t = $t;
        trace!("inspect {}: {:?}", $s, t);
        t
    })
);

/// Mime, or Media Type. Encapsulates common registers types.
///
/// Consider that a traditional mime type contains a "top level type",
/// a "sub level type", and 0-N "parameters". And they're all strings.
/// Strings everywhere. Strings mean typos. Rust has type safety. We should
/// use types!
///
/// So, Mime bundles together this data into types so the compiler can catch
/// your typos.
///
/// This improves things so you use match without Strings:
///
/// ```rust
/// # #![allow(unstable)]
/// use mime::{Mime, TopLevel, SubLevel};
///
/// let mime: mime::Mime = "application/json".parse().unwrap();
///
/// match mime {
///     Mime(TopLevel::Application, SubLevel::Json, _) => println!("matched json!"),
///     _ => ()
/// }
/// ```
#[derive(Clone, PartialEq, Debug)]
pub struct Mime(pub TopLevel, pub SubLevel, pub Vec<Param>);

macro_rules! enoom {
    (pub enum $en:ident; $ext:ident; $($ty:ident, $text:expr;)*) => (

        #[derive(Clone, Debug)]
        pub enum $en {
            $($ty),*,
            $ext(String)
        }

        impl PartialEq for $en {
            fn eq(&self, other: &$en) -> bool {
                match (self, other) {
                    $( (&$en::$ty, &$en::$ty) => true ),*,
                    (&$en::$ext(ref a), &$en::$ext(ref b)) => a == b,
                    _ => self.to_string() == other.to_string()
                }
            }
        }

        impl fmt::Display for $en {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                fmt.write_str(match *self {
                    $($en::$ty => $text),*,
                    $en::$ext(ref s) => &**s
                })
            }
        }

        impl FromStr for $en {
            type Err = ();
            fn from_str(s: &str) -> Result<$en, ()> {
                Ok(match s {
                    $(_s if _s == $text => $en::$ty),*,
                    s => $en::$ext(inspect!(stringify!($ext), s).to_string())
                })
            }
        }
    )
}

enoom! {
    pub enum TopLevel;
    Ext;
    Star, "*";
    Text, "text";
    Image, "image";
    Audio, "audio";
    Video, "video";
    Application, "application";
    Multipart, "multipart";
    Message, "message";
    Model, "model";
}

enoom! {
    pub enum SubLevel;
    Ext;
    Star, "*";

    // common text/*
    Plain, "plain";
    Html, "html";
    Xml, "xml";
    Javascript, "javascript";
    Css, "css";

    // common application/*
    Json, "json";
    WwwFormUrlEncoded, "x-www-form-urlencoded";

    // multipart/*
    FormData, "form-data";

    // common image/*
    Png, "png";
    Gif, "gif";
    Bmp, "bmp";
    Jpeg, "jpeg";
}

enoom! {
    pub enum Attr;
    Ext;
    Charset, "charset";
    Q, "q";
}

enoom! {
    pub enum Value;
    Ext;
    Utf8, "utf-8";
}

pub type Param = (Attr, Value);

impl fmt::Display for Mime {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let Mime(ref top, ref sub, ref params) = *self;
        try!(write!(fmt, "{}/{}", top, sub));
        fmt_params(&**params, fmt)
    }
}

impl FromStr for Mime {
    type Err = ();
    fn from_str(raw: &str) -> Result<Mime, ()> {
        let ascii = raw.to_ascii_lowercase(); // lifetimes :(
        let raw = &*ascii;
        let len = raw.len();
        let mut iter = raw.chars().enumerate();
        let mut params = vec![];
        // toplevel
        let mut start;
        let mut top;
        loop {
            match inspect!("top iter", iter.next()) {
                Some((0, c)) if is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > 0 && is_restricted_name_char(c) => (),
                Some((i, '/')) if i > 0 => match FromStr::from_str(&raw[..i]) {
                    Ok(t) => {
                        top = t;
                        start = i + 1;
                        break;
                    }
                    Err(_) => return Err(())
                },
                _ => return Err(()) // EOF and no toplevel is no Mime
            };

        }

        // sublevel
        let mut sub;
        loop {
            match inspect!("sub iter", iter.next()) {
                Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > start && is_restricted_name_char(c) => (),
                Some((i, ';')) if i > start => match FromStr::from_str(&raw[start..i]) {
                    Ok(s) => {
                        sub = s;
                        start = i + 1;
                        break;
                    }
                    Err(_) => return Err(())
                },
                None => match FromStr::from_str(&raw[start..]) {
                    Ok(s) => return Ok(Mime(top, s, params)),
                    Err(_) => return Err(())
                },
                _ => return Err(())
            };
        }

        // params
        debug!("starting params, len={}", len);
        loop {
            match inspect!("param", param_from_str(raw, &mut iter, start)) {
                Some((p, end)) => {
                    params.push(p);
                    start = end;
                    if start >= len {
                        break;
                    }
                }
                None => break
            }
        }

        Ok(Mime(top, sub, params))
    }
}

fn param_from_str(raw: &str, iter: &mut Enumerate<Chars>, mut start: usize) -> Option<(Param, usize)> {
    let mut attr;
    debug!("param_from_str, start={}", start);
    loop {
        match inspect!("attr iter", iter.next()) {
            Some((i, ' ')) if i == start => start = i + 1,
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, c)) if i > start && is_restricted_name_char(c) => (),
            Some((i, '=')) if i > start => match FromStr::from_str(&raw[start..i]) {
                Ok(a) => {
                    attr = inspect!("attr", a);
                    start = i + 1;
                    break;
                },
                Err(_) => return None
            },
            _ => return None
        }
    }
    let mut value;
    // values must be restrict-name-char or "anything goes"
    let mut is_quoted = false;
    loop {
        match inspect!("value iter", iter.next()) {
            Some((i, '"')) if i == start => {
                debug!("quoted");
                is_quoted = true;
                start = i + 1;
            },
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, '"')) if i > start && is_quoted => match FromStr::from_str(&raw[start..i]) {
                Ok(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                Err(_) => return None
            },
            Some((i, c)) if i > start && is_quoted || is_restricted_name_char(c) => (),
            Some((i, ';')) if i > start => match FromStr::from_str(&raw[start..i]) {
                Ok(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                Err(_) => return None
            },
            None => match FromStr::from_str(&raw[start..]) {
                Ok(v) => {
                    value = v;
                    start = raw.len();
                    break;
                },
                Err(_) => return None
            },

            _ => return None
        }
    }

    Some(((attr, value), start))
}

// From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2):
//
// > All registered media types MUST be assigned top-level type and
// > subtype names.  The combination of these names serves to uniquely
// > identify the media type, and the subtype name facet (or the absence
// > of one) identifies the registration tree.  Both top-level type and
// > subtype names are case-insensitive.
// >
// > Type and subtype names MUST conform to the following ABNF:
// >
// >     type-name = restricted-name
// >     subtype-name = restricted-name
// >
// >     restricted-name = restricted-name-first *126restricted-name-chars
// >     restricted-name-first  = ALPHA / DIGIT
// >     restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
// >                              "$" / "&" / "-" / "^" / "_"
// >     restricted-name-chars =/ "." ; Characters before first dot always
// >                                  ; specify a facet name
// >     restricted-name-chars =/ "+" ; Characters after last plus always
// >                                  ; specify a structured syntax suffix
//
fn is_restricted_name_first_char(c: char) -> bool {
    match c {
        'a'...'z' |
        '0'...'9' => true,
        _ => false
    }
}

fn is_restricted_name_char(c: char) -> bool {
    if is_restricted_name_first_char(c) {
        true
    } else {
        match c {
            '!' |
            '#' |
            '$' |
            '&' |
            '-' |
            '^' |
            '.' |
            '+' |
            '_' => true,
            _ => false
        }
    }
}

#[inline]
fn fmt_params(params: &[Param], fmt: &mut fmt::Formatter) -> fmt::Result {
    for param in params.iter() {
        try!(fmt_param(param, fmt));
    }
    Ok(())
}

#[inline]
fn fmt_param(param: &Param, fmt: &mut fmt::Formatter) -> fmt::Result {
    let (ref attr, ref value) = *param;
    write!(fmt, "; {}={}", attr, value)
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use test::Bencher;
    use super::{TopLevel, SubLevel, Attr, Value, Mime};

    #[test]
    fn test_mime_show() {
        let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![]);
        assert_eq!(mime.to_string(), "text/plain".to_string());
        let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)]);
        assert_eq!(mime.to_string(), "text/plain; charset=utf-8".to_string());
    }

    #[test]
    fn test_mime_from_str() {
        assert_eq!(FromStr::from_str("text/plain"), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![])));
        assert_eq!(FromStr::from_str("TEXT/PLAIN"), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8"), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)])));
        assert_eq!(FromStr::from_str("text/plain;charset=\"utf-8\""), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8; foo=bar"),
            Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8),
                                        (Attr::Ext("foo".to_string()), Value::Ext("bar".to_string())) ])));
    }

    #[bench]
    fn bench_show(b: &mut Bencher) {
        let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8), (Attr::Ext("foo".to_string()), Value::Ext("bar".to_string()))]);
        b.bytes = mime.to_string().as_bytes().len() as u64;
        b.iter(|| mime.to_string())
    }

    #[bench]
    fn bench_from_str(b: &mut Bencher) {
        let s = "text/plain; charset=utf-8; foo=bar";
        b.bytes = s.as_bytes().len() as u64;
        b.iter(|| s.parse::<Mime>())
    }
}
<commit_msg>Remove warning about unknown feature 'core'<commit_after>//! # Mime
//!
//! Mime is now Media Type, technically, but `Mime` is more immediately
//! understandable, so the main type here is `Mime`.
//!
//! ## What is Mime?
//!
//! Example mime string: `text/plain;charset=utf-8`
//!
//! ```rust
//! # #![allow(unstable)]
//! # use mime::Mime;
//! # use mime::TopLevel::Text;
//! # use mime::SubLevel::Plain;
//! # use mime::Attr::Charset;
//! # use mime::Value::Utf8;
//! let mime: Mime = "text/plain;charset=utf-8".parse().unwrap();
//! assert_eq!(mime, Mime(Text, Plain, vec![(Charset, Utf8)]));
//! ```

#![doc(html_root_url = "http://hyperium.github.io/mime.rs")]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(test, feature(test))]
#![feature(std_misc)]

#[macro_use]
extern crate log;

#[cfg(test)]
extern crate test;

use std::ascii::AsciiExt;
use std::fmt;
use std::str::FromStr;
use std::iter::Enumerate;
use std::str::Chars;

macro_rules! inspect(
    ($s:expr, $t:expr) => ({
        let t = $t;
        trace!("inspect {}: {:?}", $s, t);
        t
    })
);

/// Mime, or Media Type. Encapsulates common registers types.
///
/// Consider that a traditional mime type contains a "top level type",
/// a "sub level type", and 0-N "parameters". And they're all strings.
/// Strings everywhere. Strings mean typos. Rust has type safety. We should
/// use types!
///
/// So, Mime bundles together this data into types so the compiler can catch
/// your typos.
///
/// This improves things so you use match without Strings:
///
/// ```rust
/// # #![allow(unstable)]
/// use mime::{Mime, TopLevel, SubLevel};
///
/// let mime: mime::Mime = "application/json".parse().unwrap();
///
/// match mime {
///     Mime(TopLevel::Application, SubLevel::Json, _) => println!("matched json!"),
///     _ => ()
/// }
/// ```
#[derive(Clone, PartialEq, Debug)]
pub struct Mime(pub TopLevel, pub SubLevel, pub Vec<Param>);

macro_rules! enoom {
    (pub enum $en:ident; $ext:ident; $($ty:ident, $text:expr;)*) => (

        #[derive(Clone, Debug)]
        pub enum $en {
            $($ty),*,
            $ext(String)
        }

        impl PartialEq for $en {
            fn eq(&self, other: &$en) -> bool {
                match (self, other) {
                    $( (&$en::$ty, &$en::$ty) => true ),*,
                    (&$en::$ext(ref a), &$en::$ext(ref b)) => a == b,
                    _ => self.to_string() == other.to_string()
                }
            }
        }

        impl fmt::Display for $en {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                fmt.write_str(match *self {
                    $($en::$ty => $text),*,
                    $en::$ext(ref s) => &**s
                })
            }
        }

        impl FromStr for $en {
            type Err = ();
            fn from_str(s: &str) -> Result<$en, ()> {
                Ok(match s {
                    $(_s if _s == $text => $en::$ty),*,
                    s => $en::$ext(inspect!(stringify!($ext), s).to_string())
                })
            }
        }
    )
}

enoom! {
    pub enum TopLevel;
    Ext;
    Star, "*";
    Text, "text";
    Image, "image";
    Audio, "audio";
    Video, "video";
    Application, "application";
    Multipart, "multipart";
    Message, "message";
    Model, "model";
}

enoom! {
    pub enum SubLevel;
    Ext;
    Star, "*";

    // common text/*
    Plain, "plain";
    Html, "html";
    Xml, "xml";
    Javascript, "javascript";
    Css, "css";

    // common application/*
    Json, "json";
    WwwFormUrlEncoded, "x-www-form-urlencoded";

    // multipart/*
    FormData, "form-data";

    // common image/*
    Png, "png";
    Gif, "gif";
    Bmp, "bmp";
    Jpeg, "jpeg";
}

enoom! {
    pub enum Attr;
    Ext;
    Charset, "charset";
    Q, "q";
}

enoom! {
    pub enum Value;
    Ext;
    Utf8, "utf-8";
}

pub type Param = (Attr, Value);

impl fmt::Display for Mime {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let Mime(ref top, ref sub, ref params) = *self;
        try!(write!(fmt, "{}/{}", top, sub));
        fmt_params(&**params, fmt)
    }
}

impl FromStr for Mime {
    type Err = ();
    fn from_str(raw: &str) -> Result<Mime, ()> {
        let ascii = raw.to_ascii_lowercase(); // lifetimes :(
        let raw = &*ascii;
        let len = raw.len();
        let mut iter = raw.chars().enumerate();
        let mut params = vec![];
        // toplevel
        let mut start;
        let mut top;
        loop {
            match inspect!("top iter", iter.next()) {
                Some((0, c)) if is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > 0 && is_restricted_name_char(c) => (),
                Some((i, '/')) if i > 0 => match FromStr::from_str(&raw[..i]) {
                    Ok(t) => {
                        top = t;
                        start = i + 1;
                        break;
                    }
                    Err(_) => return Err(())
                },
                _ => return Err(()) // EOF and no toplevel is no Mime
            };

        }

        // sublevel
        let mut sub;
        loop {
            match inspect!("sub iter", iter.next()) {
                Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > start && is_restricted_name_char(c) => (),
                Some((i, ';')) if i > start => match FromStr::from_str(&raw[start..i]) {
                    Ok(s) => {
                        sub = s;
                        start = i + 1;
                        break;
                    }
                    Err(_) => return Err(())
                },
                None => match FromStr::from_str(&raw[start..]) {
                    Ok(s) => return Ok(Mime(top, s, params)),
                    Err(_) => return Err(())
                },
                _ => return Err(())
            };
        }

        // params
        debug!("starting params, len={}", len);
        loop {
            match inspect!("param", param_from_str(raw, &mut iter, start)) {
                Some((p, end)) => {
                    params.push(p);
                    start = end;
                    if start >= len {
                        break;
                    }
                }
                None => break
            }
        }

        Ok(Mime(top, sub, params))
    }
}

fn param_from_str(raw: &str, iter: &mut Enumerate<Chars>, mut start: usize) -> Option<(Param, usize)> {
    let mut attr;
    debug!("param_from_str, start={}", start);
    loop {
        match inspect!("attr iter", iter.next()) {
            Some((i, ' ')) if i == start => start = i + 1,
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, c)) if i > start && is_restricted_name_char(c) => (),
            Some((i, '=')) if i > start => match FromStr::from_str(&raw[start..i]) {
                Ok(a) => {
                    attr = inspect!("attr", a);
                    start = i + 1;
                    break;
                },
                Err(_) => return None
            },
            _ => return None
        }
    }
    let mut value;
    // values must be restrict-name-char or "anything goes"
    let mut is_quoted = false;
    loop {
        match inspect!("value iter", iter.next()) {
            Some((i, '"')) if i == start => {
                debug!("quoted");
                is_quoted = true;
                start = i + 1;
            },
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, '"')) if i > start && is_quoted => match FromStr::from_str(&raw[start..i]) {
                Ok(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                Err(_) => return None
            },
            Some((i, c)) if i > start && is_quoted || is_restricted_name_char(c) => (),
            Some((i, ';')) if i > start => match FromStr::from_str(&raw[start..i]) {
                Ok(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                Err(_) => return None
            },
            None => match FromStr::from_str(&raw[start..]) {
                Ok(v) => {
                    value = v;
                    start = raw.len();
                    break;
                },
                Err(_) => return None
            },

            _ => return None
        }
    }

    Some(((attr, value), start))
}

// From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2):
//
// > All registered media types MUST be assigned top-level type and
// > subtype names.  The combination of these names serves to uniquely
// > identify the media type, and the subtype name facet (or the absence
// > of one) identifies the registration tree.  Both top-level type and
// > subtype names are case-insensitive.
// >
// > Type and subtype names MUST conform to the following ABNF:
// >
// >     type-name = restricted-name
// >     subtype-name = restricted-name
// >
// >     restricted-name = restricted-name-first *126restricted-name-chars
// >     restricted-name-first  = ALPHA / DIGIT
// >     restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
// >                              "$" / "&" / "-" / "^" / "_"
// >     restricted-name-chars =/ "." ; Characters before first dot always
// >                                  ; specify a facet name
// >     restricted-name-chars =/ "+" ; Characters after last plus always
// >                                  ; specify a structured syntax suffix
//
fn is_restricted_name_first_char(c: char) -> bool {
    match c {
        'a'...'z' |
        '0'...'9' => true,
        _ => false
    }
}

fn is_restricted_name_char(c: char) -> bool {
    if is_restricted_name_first_char(c) {
        true
    } else {
        match c {
            '!' |
            '#' |
            '$' |
            '&' |
            '-' |
            '^' |
            '.' |
            '+' |
            '_' => true,
            _ => false
        }
    }
}

#[inline]
fn fmt_params(params: &[Param], fmt: &mut fmt::Formatter) -> fmt::Result {
    for param in params.iter() {
        try!(fmt_param(param, fmt));
    }
    Ok(())
}

#[inline]
fn fmt_param(param: &Param, fmt: &mut fmt::Formatter) -> fmt::Result {
    let (ref attr, ref value) = *param;
    write!(fmt, "; {}={}", attr, value)
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use test::Bencher;
    use super::{TopLevel, SubLevel, Attr, Value, Mime};

    #[test]
    fn test_mime_show() {
        let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![]);
        assert_eq!(mime.to_string(), "text/plain".to_string());
        let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)]);
        assert_eq!(mime.to_string(), "text/plain; charset=utf-8".to_string());
    }

    #[test]
    fn test_mime_from_str() {
        assert_eq!(FromStr::from_str("text/plain"), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![])));
        assert_eq!(FromStr::from_str("TEXT/PLAIN"), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8"), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)])));
        assert_eq!(FromStr::from_str("text/plain;charset=\"utf-8\""), Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8; foo=bar"),
            Ok(Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8),
                                        (Attr::Ext("foo".to_string()), Value::Ext("bar".to_string())) ])));
    }

    #[bench]
    fn bench_show(b: &mut Bencher) {
        let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8), (Attr::Ext("foo".to_string()), Value::Ext("bar".to_string()))]);
        b.bytes = mime.to_string().as_bytes().len() as u64;
        b.iter(|| mime.to_string())
    }

    #[bench]
    fn bench_from_str(b: &mut Bencher) {
        let s = "text/plain; charset=utf-8; foo=bar";
        b.bytes = s.as_bytes().len() as u64;
        b.iter(|| s.parse::<Mime>())
    }
}
<|endoftext|>


<commit_before>//! Compile-time assertions to ensure that invariants are met.
//!
//! # Usage
//!
//! This crate is available [on crates.io][crate] and can be used by adding the
//! following to your project's `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! static_assertions = "0.1"
//! ```
//!
//! and this to your crate root:
//!
//! ```
//! #[macro_use]
//! extern crate static_assertions;
//! # fn main() {}
//! ```
//!
//! # Assert Equal Size
//!
//! When performing operations such as pointer casts or dealing with [`usize`]
//! versus [`u64`] versus [`u32`], the size of your types matter. This is where
//! [`assert_eq_size`] comes into play. Types provided as arguments to
//! [`assert_eq_size`] are ensured to be the same size at compile-time. If the
//! types differ in size, the code will fail to compile.
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! assert_eq_size!([u8; 4], (u16, u16), u32);
//!
//! // Produces a compilation failure:
//! // assert_eq_size!(u32, u8);
//! # }
//! ```
//!
//! Similar to [`assert_eq_size`], there is [`assert_eq_size_val`]. Instead of
//! specifying types to compare, values' sizes can be directly compared against
//! each other.
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! let x = 42u8;
//! let y = true;
//!
//! assert_eq_size_val!(x, y);
//! # }
//! ```
//! [`assert_eq_size_val`] doesn't consume its arguments and thus works for
//! non-[`Clone`]able values.
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! struct Buffer([u8; 256]);
//!
//! let buf = Buffer([0; 256]);
//! let val = [0u64; 32];
//!
//! assert_eq_size_val!(buf, val);
//!
//! // `buf` and `val` can be used here
//! # }
//! ```
//!
//! # Assert Constant Expression
//!
//! Constant expressions can be ensured to have certain properties via
//! [`const_assert`]. If the expression evaluates to `false`, the file will fail
//! to compile. This is synonymous to [`static_assert` in C++][static_assert].
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! const NUM: usize = 2;
//!
//! const_assert!(NUM * NUM == 4);
//! # }
//! ```
//!
//! **Limitation:** Due to implementation details, [`assert_eq_size`] and
//! [`const_assert`] can only be used from within the context of a function.
//!
//! [crate]: https://crates.io/crates/static_assertions
//! [static_assert]: http://en.cppreference.com/w/cpp/language/static_assert
//! [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html
//! [`usize`]: https://doc.rust-lang.org/std/primitive.usize.html
//! [`u64`]: https://doc.rust-lang.org/std/primitive.u64.html
//! [`u32`]: https://doc.rust-lang.org/std/primitive.u32.html
//! [`assert_eq_size_val`]: macro.assert_eq_size_val.html
//! [`assert_eq_size`]: macro.assert_eq_size.html
//! [`const_assert`]: macro.const_assert.html

#![no_std]

#[doc(hidden)]
pub extern crate core as _core;

/// Asserts at compile-time that the types have equal sizes.
///
/// This especially is useful for when coercing pointers between different types
/// and ensuring the underlying values are the same size.
///
/// # Example
///
/// ```
/// # #[macro_use]
/// # extern crate static_assertions;
/// struct Byte(u8);
///
/// # fn main() {
/// assert_eq_size!(Byte, u8);
///
/// // Supports unlimited arguments:
/// assert_eq_size!([Byte; 4], [u16; 2], u32);
///
/// // Fails to compile:
/// // assert_eq_size!(Byte, u16);
/// # }
/// ```
#[macro_export]
macro_rules! assert_eq_size {
    ($x:ty, $($xs:ty),+) => {
        #[allow(unused_unsafe)]
        unsafe {
            use $crate::_core::mem::{forget, transmute, uninitialized};
            $(forget::<$xs>(transmute(uninitialized::<$x>()));)+
        }
    }
}

/// Asserts at compile-time that the values have equal sizes.
///
/// # Example
///
/// ```
/// # #[macro_use]
/// # extern crate static_assertions;
/// # fn main() {
/// struct Byte(u8);
///
/// let x = 10u8;
/// let y = Byte(42); // Works for non-cloneable types
///
/// assert_eq_size_val!(x, y);
/// assert_eq_size_val!(x, y, 0u8);
///
/// // Fails to compile:
/// // assert_eq_size_val!(x, 0u32);
/// # }
/// ```
#[macro_export]
macro_rules! assert_eq_size_val {
    ($x:expr, $($xs:expr),+) => {
        #[allow(unused_unsafe)]
        unsafe {
            use $crate::_core::{mem, ptr};
            let mut copy = ptr::read(&$x);
            $(ptr::write(&mut copy, mem::transmute(ptr::read(&$xs)));)+
            mem::forget(copy);
        }
    }
}

/// Asserts at compile-time that the constant expression evaluates to `true`.
///
/// # Example
///
/// ```
/// # #[macro_use]
/// # extern crate static_assertions;
/// # fn main() {
/// const_assert!(2 + 2 == 4);
///
/// const FIVE: usize = 5;
/// const_assert!(FIVE - FIVE == 0);
///
/// // Fails to compile:
/// // const_assert!(1 >= 2);
/// # }
/// ```
#[macro_export]
macro_rules! const_assert {
    ($cond:expr) => {
        // Causes overflow if condition is false
        let _ = [(); 0 - (!($cond) as usize)];
    };
    ($($xs:expr),+) => {
        const_assert!($($xs)&&+);
    };
    ($($xs:expr);+ $(;)*) => {
        const_assert!($($xs),+);
    };
}
<commit_msg>Create const_assert_eq macro<commit_after>//! Compile-time assertions to ensure that invariants are met.
//!
//! # Usage
//!
//! This crate is available [on crates.io][crate] and can be used by adding the
//! following to your project's `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! static_assertions = "0.1"
//! ```
//!
//! and this to your crate root:
//!
//! ```
//! #[macro_use]
//! extern crate static_assertions;
//! # fn main() {}
//! ```
//!
//! # Assert Equal Size
//!
//! When performing operations such as pointer casts or dealing with [`usize`]
//! versus [`u64`] versus [`u32`], the size of your types matter. This is where
//! [`assert_eq_size`] comes into play. Types provided as arguments to
//! [`assert_eq_size`] are ensured to be the same size at compile-time. If the
//! types differ in size, the code will fail to compile.
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! assert_eq_size!([u8; 4], (u16, u16), u32);
//!
//! // Produces a compilation failure:
//! // assert_eq_size!(u32, u8);
//! # }
//! ```
//!
//! Similar to [`assert_eq_size`], there is [`assert_eq_size_val`]. Instead of
//! specifying types to compare, values' sizes can be directly compared against
//! each other.
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! let x = 42u8;
//! let y = true;
//!
//! assert_eq_size_val!(x, y);
//! # }
//! ```
//! [`assert_eq_size_val`] doesn't consume its arguments and thus works for
//! non-[`Clone`]able values.
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! struct Buffer([u8; 256]);
//!
//! let buf = Buffer([0; 256]);
//! let val = [0u64; 32];
//!
//! assert_eq_size_val!(buf, val);
//!
//! // `buf` and `val` can be used here
//! # }
//! ```
//!
//! # Assert Constant Expression
//!
//! Constant expressions can be ensured to have certain properties via
//! [`const_assert`]. If the expression evaluates to `false`, the file will fail
//! to compile. This is synonymous to [`static_assert` in C++][static_assert].
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! const NUM: usize = 2;
//!
//! const_assert!(NUM * NUM == 4);
//! # }
//! ```
//!
//! As a shorthand for `const_assert!(a == b)`, there's [`const_assert_eq`].
//!
//! ```
//! # #[macro_use]
//! # extern crate static_assertions;
//! # fn main() {
//! const NUM: usize = 32;
//!
//! const_assert_eq!(NUM + NUM, 64);
//! # }
//! ```
//!
//! **Limitation:** Due to implementation details, [`assert_eq_size`],
//! [`const_assert`], and [`const_assert_eq`] can only be used from within the
//! context of a function.
//!
//! [crate]: https://crates.io/crates/static_assertions
//! [static_assert]: http://en.cppreference.com/w/cpp/language/static_assert
//! [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html
//! [`usize`]: https://doc.rust-lang.org/std/primitive.usize.html
//! [`u64`]: https://doc.rust-lang.org/std/primitive.u64.html
//! [`u32`]: https://doc.rust-lang.org/std/primitive.u32.html
//! [`assert_eq_size_val`]: macro.assert_eq_size_val.html
//! [`assert_eq_size`]: macro.assert_eq_size.html
//! [`const_assert`]: macro.const_assert.html
//! [`const_assert_eq`]: macro.const_assert_eq.html

#![no_std]

#[doc(hidden)]
pub extern crate core as _core;

/// Asserts at compile-time that the types have equal sizes.
///
/// This especially is useful for when coercing pointers between different types
/// and ensuring the underlying values are the same size.
///
/// # Example
///
/// ```
/// # #[macro_use]
/// # extern crate static_assertions;
/// struct Byte(u8);
///
/// # fn main() {
/// assert_eq_size!(Byte, u8);
///
/// // Supports unlimited arguments:
/// assert_eq_size!([Byte; 4], [u16; 2], u32);
///
/// // Fails to compile:
/// // assert_eq_size!(Byte, u16);
/// # }
/// ```
#[macro_export]
macro_rules! assert_eq_size {
    ($x:ty, $($xs:ty),+) => {
        #[allow(unused_unsafe)]
        unsafe {
            use $crate::_core::mem::{forget, transmute, uninitialized};
            $(forget::<$xs>(transmute(uninitialized::<$x>()));)+
        }
    }
}

/// Asserts at compile-time that the values have equal sizes.
///
/// # Example
///
/// ```
/// # #[macro_use]
/// # extern crate static_assertions;
/// # fn main() {
/// struct Byte(u8);
///
/// let x = 10u8;
/// let y = Byte(42); // Works for non-cloneable types
///
/// assert_eq_size_val!(x, y);
/// assert_eq_size_val!(x, y, 0u8);
///
/// // Fails to compile:
/// // assert_eq_size_val!(x, 0u32);
/// # }
/// ```
#[macro_export]
macro_rules! assert_eq_size_val {
    ($x:expr, $($xs:expr),+) => {
        #[allow(unused_unsafe)]
        unsafe {
            use $crate::_core::{mem, ptr};
            let mut copy = ptr::read(&$x);
            $(ptr::write(&mut copy, mem::transmute(ptr::read(&$xs)));)+
            mem::forget(copy);
        }
    }
}

/// Asserts at compile-time that the constant expression evaluates to `true`.
///
/// # Example
///
/// ```
/// # #[macro_use]
/// # extern crate static_assertions;
/// # fn main() {
/// const_assert!(2 + 2 == 4);
///
/// const FIVE: usize = 5;
/// const_assert!(FIVE - FIVE == 0);
///
/// // Fails to compile:
/// // const_assert!(1 >= 2);
/// # }
/// ```
#[macro_export]
macro_rules! const_assert {
    ($cond:expr) => {
        // Causes overflow if condition is false
        let _ = [(); 0 - (!($cond) as usize)];
    };
    ($($xs:expr),+) => {
        const_assert!($($xs)&&+);
    };
    ($($xs:expr);+ $(;)*) => {
        const_assert!($($xs),+);
    };
}

/// Asserts at compile-time that the constants are equal in value.
#[macro_export]
macro_rules! const_assert_eq {
    ($x:expr, $y:expr) => {
        const_assert!($x == $y);
    }
}
<|endoftext|>


<commit_before>#[test]
fn it_works() {
}
<commit_msg>Implemented an extremely basic core library.<commit_after>#![feature(phase)]
extern crate regex;
#[phase(plugin)] extern crate regex_macros;

use std::io::{BufferedReader, BufferedWriter, InvalidInput, IoError, IoResult, TcpStream};

pub struct Bot {
    pub sock: TcpStream
}

impl Bot {
    pub fn new() -> Bot {
        let sock = TcpStream::connect("irc.fyrechat.net", 6667).unwrap();
        Bot {
            sock: sock,
        }
    }

    pub fn identify(&mut self) {
        let mut writer = BufferedWriter::new(self.sock.clone());
        writer.write_str("NICK :pickles\r\n").unwrap();
        writer.write_str("USER pickles 0 * :pickles\r\n").unwrap();
        writer.flush().unwrap();
    }

    pub fn output(&mut self) {
        let mut reader = BufferedReader::new(self.sock.clone());
        for line in reader.lines() {
            match line {
                Ok(ln) => {
                    let (source, command, args) = process(ln.as_slice()).unwrap();
                    self.handle_command(source, command, args.as_slice());
                    println!("{}", ln)
                },
                Err(e) => println!("Shit, you're fucked! {}", e),
            }
        }
    }

    fn handle_command(&mut self, source: &str, command: &str, args: &[&str]) -> () {
        match (command, args) {
            ("PING", [msg]) => {
                self.send("PONG", msg);
            },
            ("376", _) => {
                self.send("JOIN", "#vana");
            },
            ("PRIVMSG", [channel, msg]) => {
                if msg.contains("pickles") && msg.contains("hi") {
                    self.send("PRIVMSG #vana", "hi");
                }

                if msg.starts_with(". ") {
                    self.send("PRIVMSG #vana", msg.slice_from(2));
                }
            },
            _ => (),
        }
    }

    fn send(&mut self, command: &str, arg: &str) {
        let mut writer = BufferedWriter::new(self.sock.clone());
        write!(writer, "{} :{}\r\n", command, arg);
        writer.flush().unwrap();
    }
}

fn process(msg: &str) -> IoResult<(&str, &str, Vec<&str>)> {
    let reg = regex!(r"^(?::([^ ]+) )?([^ ]+)(.*)");
    let cap = match reg.captures(msg) {
        Some(x) => x,
        None => return Err(IoError {
            kind: InvalidInput,
            desc: "Failed to parse line",
            detail: None,
        }),
    };
    let source = cap.at(1);
    let command = cap.at(2);
    let args = parse_args(cap.at(3));
    Ok((source, command, args))
}

fn parse_args(line: &str) -> Vec<&str> {
    let reg = regex!(r" ([^: ]+)| :(.*)$");
    reg.captures_iter(line).map(|cap| {
        match cap.at(1) {
            "" => cap.at(2),
            x => x,
        }
    }).collect()
}
<|endoftext|>


<commit_before>#![crate_name = "spread"]
#![comment = "A Rust client library for the Spread toolkit"]
#![crate_type = "lib"]
#![license = "MIT"]

#[deny(non_camel_case_types)]

extern crate encoding;

use encoding::{Encoding, EncodeStrict, DecodeStrict};
use encoding::all::ISO_8859_1;
use std::io::{ConnectionFailed, ConnectionRefused, IoError, IoResult, OtherIoError};
use std::io::net::ip::SocketAddr;
use std::io::net::tcp::TcpStream;
use std::result::Result;
use util::{bytes_to_int, flip_endianness, int_to_bytes, same_endianness};

mod test;
mod util;

pub static DefaultSpreadPort: i16 = 4803;

static MaxPrivateNameLength: uint = 10;
static DefaultAuthName: &'static str  = "NULL";
static MaxAuthNameLength: uint = 30;
static MaxAuthMethodCount: uint = 3;
static MaxGroupNameLength: uint = 32;

// Control message types.
// NOTE: The only currently-implemented service type for messaging is "reliable".
enum ControlServiceType {
    JoinMessage     = 0x00010000,
    LeaveMessage    = 0x00020000,
    KillMessage     = 0x00040000,
    ReliableMessage = 0x00000002
}

static SpreadMajorVersion: u8 = 4;
static SpreadMinorVersion: u8 = 4;
static SpreadPatchVersion: u8 = 0;

// Error codes, as per http://www.spread.org/docs/spread_docs_4/docs/error_codes.html
pub enum SpreadError {
    AcceptSession = 1,
    IllegalSpread = -1,
    CouldNotConnection = -2,
    RejectQuota = -3,
    RejectNOName = -4,
    RejectIllegalName = -5,
    RejectNotUnique = -6,
    RejectVersion = -7,
    ConnectionClosed = -8,
    RejectAuth = -9,
    IllegalSession = -11,
    IllegalService = -12,
    IllegalMessage = -13,
    IllegalGroup = -14,
    BufferTooShort = -15,
    GroupsTooShort = -16,
    MessageTooLong = -17,
    NetErrorOnSession = -18
}

/// A message to be sent or received by a Spread client to/from a group.
pub struct SpreadMessage {
    service_type: u32,
    groups: Vec<String>,
    sender: String,
    data: Vec<u8>,
}

/// Representation of a client connection to a Spread daemon.
pub struct SpreadClient {
    stream: TcpStream,
    private_name: String,
    groups: Vec<String>,
    receive_membership_messages: bool
}

// Construct a byte vector representation of a connect message for the given
// connection arguments.
fn encode_connect_message(
    private_name: &str,
    receive_membership_messages: bool
) -> Result<Vec<u8>, String> {
    let mut vec: Vec<u8> = Vec::new();

    // Set Spread version.
    vec.push(SpreadMajorVersion);
    vec.push(SpreadMinorVersion);
    vec.push(SpreadPatchVersion);

    // Apply masks for group membership (and priority, which is unimplemented).
    let mask = if receive_membership_messages {
        0x10
    } else {
        0
    };
    vec.push(mask);

    let private_name_buf = try!(ISO_8859_1.encode(private_name, EncodeStrict).map_err(
        |_| format!("Failed to encode private name: {}", private_name)
            ));

    vec.push(private_name.char_len() as u8);
    vec.push_all_move(private_name_buf);
    Ok(vec)
}

/// Establishes a named connection to a Spread daemon running at a given
/// `SocketAddr`.
///
/// *Arguments:*
///
/// - `addr`: The address at which the Spread daemon is running.
/// - `private_name`: A name to use privately to refer to the connection.
/// - `receive_membership_messages`: If true, membership messages will be
///   received by the resultant client.
pub fn connect(
    addr: SocketAddr,
    private_name: &str,
    receive_membership_messages: bool
) -> IoResult<SpreadClient> {
    // Truncate (if necessary) and write `private_name`.
    let truncated_private_name = match private_name {
        too_long if too_long.char_len() > MaxPrivateNameLength =>
            too_long.slice_to(MaxPrivateNameLength),
        just_fine => just_fine
    };

    // Send the initial connect message.
    let connect_message = try!(encode_connect_message(
        truncated_private_name,
        receive_membership_messages
    ).map_err(|error_msg| IoError {
        kind: ConnectionFailed,
        desc: "",
        detail: Some(error_msg)
    }));

    let mut stream = try!(TcpStream::connect(addr.ip.to_string().as_slice(), addr.port));
    try!(stream.write(connect_message.as_slice()));

    // Read the authentication methods.
    let authname_len = try!(stream.read_byte()) as i32;
    if authname_len == -1 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Connection closed during connect attempt to read auth name length",
            detail: None
        });
    } else if authname_len >= 128 {
        return Err(IoError {
            kind: ConnectionRefused,
            desc: "Connection attempt rejected",
            detail: Some(format!("{}", (0xffffff00 | authname_len as u32) as i32))
        });
    }

    // Ignore the list.
    // TODO: Support IP-based auth?
    try!(stream.read_exact(authname_len as uint));

    // Send auth method choice.
    let mut authname_vec: Vec<u8> = match ISO_8859_1.encode(DefaultAuthName, EncodeStrict) {
        Ok(vec) => vec,
        Err(error) => return Err(IoError {
            kind: ConnectionFailed,
            desc: "Failed to encode authname",
            detail: Some(format!("{}", error))
        })
    };

    for _ in range(authname_len as uint, (MaxAuthNameLength * MaxAuthMethodCount + 1)) {
        authname_vec.push(0);
    }
    try!(stream.write(authname_vec.as_slice()));

    // Check for an accept message.
    let accepted: u8 = try!(stream.read_byte());
    if accepted != AcceptSession as u8 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Connection attempt rejected",
            detail: Some(format!("{}", (0xffffff00 | accepted as u32) as i32))
        });
    }

    // Read the version of Spread that the server is running.
    let (major, minor, patch) =
        (try!(stream.read_byte()) as i32, try!(stream.read_byte()) as i32, try!(stream.read_byte()) as i32);

    if major == -1 || minor == -1 || patch == -1 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Invalid version returned from server",
            detail: Some(format!("{}.{}.{}", major, minor, patch))
        });
    }

    let version_sum = (major*10000) + (minor*100) + patch;
    if version_sum < 30100 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Server is running old, unsupported version of Spread",
            detail: Some(format!("{}.{}.{}", major, minor, patch))
        });
    }

    // Read the private group name.
    let group_name_len = try!(stream.read_byte()) as i32;
    if group_name_len == -1 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Connection closed during connect attempt to read group name length",
            detail: None
        });
    }
    let group_name_buf = try!(stream.read_exact(group_name_len as uint));
    let private_group_name = match String::from_utf8(group_name_buf) {
        Ok(group_name) => group_name,
        Err(error) => return Err(IoError {
            kind: ConnectionFailed,
            desc: "Server sent invalid group name",
            detail: Some(format!("{}", error))
        })
    };

    Ok(SpreadClient {
        stream: stream,
        private_name: private_group_name,
        groups: Vec::new(),
        receive_membership_messages: receive_membership_messages
    })
}

impl SpreadClient {
    // Encode a service message for dispatch to a Spread daemon.
    fn encode_message(
        service_type: u32,
        private_name: &str,
        groups: &[&str],
        data: &[u8]
    ) -> Result<Vec<u8>, String> {
        let mut vec: Vec<u8> = Vec::new();
        vec.push_all_move(int_to_bytes(service_type));

        let private_name_buf = try!(ISO_8859_1.encode(private_name, EncodeStrict).map_err(
            |_| format!("Failed to encode private name: {}", private_name)
        ));
        vec.push_all_move(private_name_buf);
        for _ in range(private_name.len(), (MaxGroupNameLength)) {
            vec.push(0);
        }

        vec.push_all_move(int_to_bytes(groups.len() as u32));
        vec.push_all_move(int_to_bytes(0));
        vec.push_all_move(int_to_bytes(data.len() as u32));

        // Encode and push each group name, converting any encoding errors
        // to error message strings.
        for group in groups.iter() {
            let group_buf = try!(ISO_8859_1.encode(*group, EncodeStrict).map_err(
                |_| format!("Failed to encode group name: {}", group)
            ));
            vec.push_all_move(group_buf);
            for _ in range(group.len(), (MaxGroupNameLength)) {
                vec.push(0);
            }
        }

        vec.push_all(data);
        Ok(vec)
    }

    /// Disconnects the client from the Spread daemon.
    // TODO: Prevent further usage of client?
    pub fn disconnect(&mut self) -> IoResult<()> {
        let name_slice = self.private_name.as_slice();
        let kill_message = try!(SpreadClient::encode_message(
            KillMessage as u32,
            name_slice,
            [name_slice],
            []
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        self.stream.write(kill_message.as_slice())
    }

    /// Join a named Spread group.
    ///
    /// All messages sent to the group will be received by the client until it
    /// has left the group.
    pub fn join(&mut self, group_name: &str) -> IoResult<()> {
        let join_message = try!(SpreadClient::encode_message(
            JoinMessage as u32,
            self.private_name.as_slice(),
            [group_name],
            []
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        try!(self.stream.write(join_message.as_slice()));
        self.groups.push(group_name.to_string());
        Ok(())
    }

    /// Leave a named Spread group.
    pub fn leave(&mut self, group_name: &str) -> IoResult<()> {
        let leave_message = try!(SpreadClient::encode_message(
            LeaveMessage as u32,
            self.private_name.as_slice(),
            [group_name],
            []
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        try!(self.stream.write(leave_message.as_slice()));
        self.groups.push(group_name.to_string());
        Ok(())
    }

    /// Send a message to a set of named groups.
    pub fn multicast(
        &mut self,
        groups: &[&str],
        data: &[u8]
    ) -> IoResult<()> {
        let message = try!(SpreadClient::encode_message(
            ReliableMessage as u32,
            self.private_name.as_slice(),
            groups,
            data
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        self.stream.write(message.as_slice())
    }

    /// Receive the next available message. If there are no messages available,
    /// the call will block until either a message is received or a timeout
    /// expires.
    pub fn receive(&mut self) -> IoResult<SpreadMessage> {
        // Header format (sizes in bytes):
        //   svc_type:   4
        //   sender:    32
        //   num_groups: 4
        //   hint:       4
        //   data_len:   4
        let header_vec = try!(self.stream.read_exact(MaxGroupNameLength + 16));
        let is_correct_endianness = same_endianness(bytes_to_int(header_vec.slice(0, 4)));

        let svc_type = match (is_correct_endianness, bytes_to_int(header_vec.slice(0, 4))) {
            (true, correct) => correct,
            (false, incorrect) => flip_endianness(incorrect)
        };

        let sender = try!(
            ISO_8859_1.decode(header_vec.slice(4, 36), DecodeStrict).map_err(|error| IoError {
                kind: OtherIoError,
                desc: "Decoding sender name failed",
                detail: Some(String::from_str(error.as_slice()))
            })
        );

        let num_groups = match (is_correct_endianness, bytes_to_int(header_vec.slice(36, 40))) {
            (true, correct) => correct,
            (false, incorrect) => flip_endianness(incorrect)
        };
        let data_len = match (is_correct_endianness, bytes_to_int(header_vec.slice(44, 48))) {
            (true, correct) => correct,
            (false, incorrect) => flip_endianness(incorrect)
        };

        // Groups format (sizes in bytes):
        //   groups: num_groups
        let groups_vec =
            try!(self.stream.read_exact(MaxGroupNameLength * num_groups as uint));
        let mut groups = Vec::new();

        for n in range(0, num_groups) {
            let i: uint = n as uint * MaxGroupNameLength;
            let group = try!(
                ISO_8859_1.decode(groups_vec.slice(i, i + MaxGroupNameLength), DecodeStrict)
                    .map_err(|error| IoError {
                        kind: OtherIoError,
                        desc: "Decoding group name failed",
                        detail: Some(String::from_str(error.as_slice()))
                    }));
            groups.push(group);
        }

        // Data format (sizes in bytes):
        //   data: data_len
        let data_vec = try!(self.stream.read_exact(data_len as uint));

        Ok(SpreadMessage {
            service_type: svc_type as u32,
            groups: groups,
            sender: sender,
            data: data_vec
        })
    }
}
<commit_msg>Make certain Spread{Message,Client} fields public.<commit_after>#![crate_name = "spread"]
#![comment = "A Rust client library for the Spread toolkit"]
#![crate_type = "lib"]
#![license = "MIT"]

#[deny(non_camel_case_types)]

extern crate encoding;

use encoding::{Encoding, EncodeStrict, DecodeStrict};
use encoding::all::ISO_8859_1;
use std::io::{ConnectionFailed, ConnectionRefused, IoError, IoResult, OtherIoError};
use std::io::net::ip::SocketAddr;
use std::io::net::tcp::TcpStream;
use std::result::Result;
use util::{bytes_to_int, flip_endianness, int_to_bytes, same_endianness};

mod test;
mod util;

pub static DefaultSpreadPort: i16 = 4803;

static MaxPrivateNameLength: uint = 10;
static DefaultAuthName: &'static str  = "NULL";
static MaxAuthNameLength: uint = 30;
static MaxAuthMethodCount: uint = 3;
static MaxGroupNameLength: uint = 32;

// Control message types.
// NOTE: The only currently-implemented service type for messaging is "reliable".
enum ControlServiceType {
    JoinMessage     = 0x00010000,
    LeaveMessage    = 0x00020000,
    KillMessage     = 0x00040000,
    ReliableMessage = 0x00000002
}

static SpreadMajorVersion: u8 = 4;
static SpreadMinorVersion: u8 = 4;
static SpreadPatchVersion: u8 = 0;

// Error codes, as per http://www.spread.org/docs/spread_docs_4/docs/error_codes.html
pub enum SpreadError {
    AcceptSession = 1,
    IllegalSpread = -1,
    CouldNotConnection = -2,
    RejectQuota = -3,
    RejectNOName = -4,
    RejectIllegalName = -5,
    RejectNotUnique = -6,
    RejectVersion = -7,
    ConnectionClosed = -8,
    RejectAuth = -9,
    IllegalSession = -11,
    IllegalService = -12,
    IllegalMessage = -13,
    IllegalGroup = -14,
    BufferTooShort = -15,
    GroupsTooShort = -16,
    MessageTooLong = -17,
    NetErrorOnSession = -18
}

/// A message to be sent or received by a Spread client to/from a group.
pub struct SpreadMessage {
    service_type: u32,
    pub groups: Vec<String>,
    pub sender: String,
    pub data: Vec<u8>,
}

/// Representation of a client connection to a Spread daemon.
pub struct SpreadClient {
    stream: TcpStream,
    pub private_name: String,
    pub groups: Vec<String>,
    receive_membership_messages: bool
}

// Construct a byte vector representation of a connect message for the given
// connection arguments.
fn encode_connect_message(
    private_name: &str,
    receive_membership_messages: bool
) -> Result<Vec<u8>, String> {
    let mut vec: Vec<u8> = Vec::new();

    // Set Spread version.
    vec.push(SpreadMajorVersion);
    vec.push(SpreadMinorVersion);
    vec.push(SpreadPatchVersion);

    // Apply masks for group membership (and priority, which is unimplemented).
    let mask = if receive_membership_messages {
        0x10
    } else {
        0
    };
    vec.push(mask);

    let private_name_buf = try!(ISO_8859_1.encode(private_name, EncodeStrict).map_err(
        |_| format!("Failed to encode private name: {}", private_name)
            ));

    vec.push(private_name.char_len() as u8);
    vec.push_all_move(private_name_buf);
    Ok(vec)
}

/// Establishes a named connection to a Spread daemon running at a given
/// `SocketAddr`.
///
/// *Arguments:*
///
/// - `addr`: The address at which the Spread daemon is running.
/// - `private_name`: A name to use privately to refer to the connection.
/// - `receive_membership_messages`: If true, membership messages will be
///   received by the resultant client.
pub fn connect(
    addr: SocketAddr,
    private_name: &str,
    receive_membership_messages: bool
) -> IoResult<SpreadClient> {
    // Truncate (if necessary) and write `private_name`.
    let truncated_private_name = match private_name {
        too_long if too_long.char_len() > MaxPrivateNameLength =>
            too_long.slice_to(MaxPrivateNameLength),
        just_fine => just_fine
    };

    // Send the initial connect message.
    let connect_message = try!(encode_connect_message(
        truncated_private_name,
        receive_membership_messages
    ).map_err(|error_msg| IoError {
        kind: ConnectionFailed,
        desc: "",
        detail: Some(error_msg)
    }));

    let mut stream = try!(TcpStream::connect(addr.ip.to_string().as_slice(), addr.port));
    try!(stream.write(connect_message.as_slice()));

    // Read the authentication methods.
    let authname_len = try!(stream.read_byte()) as i32;
    if authname_len == -1 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Connection closed during connect attempt to read auth name length",
            detail: None
        });
    } else if authname_len >= 128 {
        return Err(IoError {
            kind: ConnectionRefused,
            desc: "Connection attempt rejected",
            detail: Some(format!("{}", (0xffffff00 | authname_len as u32) as i32))
        });
    }

    // Ignore the list.
    // TODO: Support IP-based auth?
    try!(stream.read_exact(authname_len as uint));

    // Send auth method choice.
    let mut authname_vec: Vec<u8> = match ISO_8859_1.encode(DefaultAuthName, EncodeStrict) {
        Ok(vec) => vec,
        Err(error) => return Err(IoError {
            kind: ConnectionFailed,
            desc: "Failed to encode authname",
            detail: Some(format!("{}", error))
        })
    };

    for _ in range(authname_len as uint, (MaxAuthNameLength * MaxAuthMethodCount + 1)) {
        authname_vec.push(0);
    }
    try!(stream.write(authname_vec.as_slice()));

    // Check for an accept message.
    let accepted: u8 = try!(stream.read_byte());
    if accepted != AcceptSession as u8 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Connection attempt rejected",
            detail: Some(format!("{}", (0xffffff00 | accepted as u32) as i32))
        });
    }

    // Read the version of Spread that the server is running.
    let (major, minor, patch) =
        (try!(stream.read_byte()) as i32, try!(stream.read_byte()) as i32, try!(stream.read_byte()) as i32);

    if major == -1 || minor == -1 || patch == -1 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Invalid version returned from server",
            detail: Some(format!("{}.{}.{}", major, minor, patch))
        });
    }

    let version_sum = (major*10000) + (minor*100) + patch;
    if version_sum < 30100 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Server is running old, unsupported version of Spread",
            detail: Some(format!("{}.{}.{}", major, minor, patch))
        });
    }

    // Read the private group name.
    let group_name_len = try!(stream.read_byte()) as i32;
    if group_name_len == -1 {
        return Err(IoError {
            kind: ConnectionFailed,
            desc: "Connection closed during connect attempt to read group name length",
            detail: None
        });
    }
    let group_name_buf = try!(stream.read_exact(group_name_len as uint));
    let private_group_name = match String::from_utf8(group_name_buf) {
        Ok(group_name) => group_name,
        Err(error) => return Err(IoError {
            kind: ConnectionFailed,
            desc: "Server sent invalid group name",
            detail: Some(format!("{}", error))
        })
    };

    Ok(SpreadClient {
        stream: stream,
        private_name: private_group_name,
        groups: Vec::new(),
        receive_membership_messages: receive_membership_messages
    })
}

impl SpreadClient {
    // Encode a service message for dispatch to a Spread daemon.
    fn encode_message(
        service_type: u32,
        private_name: &str,
        groups: &[&str],
        data: &[u8]
    ) -> Result<Vec<u8>, String> {
        let mut vec: Vec<u8> = Vec::new();
        vec.push_all_move(int_to_bytes(service_type));

        let private_name_buf = try!(ISO_8859_1.encode(private_name, EncodeStrict).map_err(
            |_| format!("Failed to encode private name: {}", private_name)
        ));
        vec.push_all_move(private_name_buf);
        for _ in range(private_name.len(), (MaxGroupNameLength)) {
            vec.push(0);
        }

        vec.push_all_move(int_to_bytes(groups.len() as u32));
        vec.push_all_move(int_to_bytes(0));
        vec.push_all_move(int_to_bytes(data.len() as u32));

        // Encode and push each group name, converting any encoding errors
        // to error message strings.
        for group in groups.iter() {
            let group_buf = try!(ISO_8859_1.encode(*group, EncodeStrict).map_err(
                |_| format!("Failed to encode group name: {}", group)
            ));
            vec.push_all_move(group_buf);
            for _ in range(group.len(), (MaxGroupNameLength)) {
                vec.push(0);
            }
        }

        vec.push_all(data);
        Ok(vec)
    }

    /// Disconnects the client from the Spread daemon.
    // TODO: Prevent further usage of client?
    pub fn disconnect(&mut self) -> IoResult<()> {
        let name_slice = self.private_name.as_slice();
        let kill_message = try!(SpreadClient::encode_message(
            KillMessage as u32,
            name_slice,
            [name_slice],
            []
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        self.stream.write(kill_message.as_slice())
    }

    /// Join a named Spread group.
    ///
    /// All messages sent to the group will be received by the client until it
    /// has left the group.
    pub fn join(&mut self, group_name: &str) -> IoResult<()> {
        let join_message = try!(SpreadClient::encode_message(
            JoinMessage as u32,
            self.private_name.as_slice(),
            [group_name],
            []
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        try!(self.stream.write(join_message.as_slice()));
        self.groups.push(group_name.to_string());
        Ok(())
    }

    /// Leave a named Spread group.
    pub fn leave(&mut self, group_name: &str) -> IoResult<()> {
        let leave_message = try!(SpreadClient::encode_message(
            LeaveMessage as u32,
            self.private_name.as_slice(),
            [group_name],
            []
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        try!(self.stream.write(leave_message.as_slice()));
        self.groups.push(group_name.to_string());
        Ok(())
    }

    /// Send a message to a set of named groups.
    pub fn multicast(
        &mut self,
        groups: &[&str],
        data: &[u8]
    ) -> IoResult<()> {
        let message = try!(SpreadClient::encode_message(
            ReliableMessage as u32,
            self.private_name.as_slice(),
            groups,
            data
        ).map_err(|error_msg| IoError {
            kind: OtherIoError,
            desc: "Disconnection failed",
            detail: Some(error_msg)
        }));

        self.stream.write(message.as_slice())
    }

    /// Receive the next available message. If there are no messages available,
    /// the call will block until either a message is received or a timeout
    /// expires.
    pub fn receive(&mut self) -> IoResult<SpreadMessage> {
        // Header format (sizes in bytes):
        //   svc_type:   4
        //   sender:    32
        //   num_groups: 4
        //   hint:       4
        //   data_len:   4
        let header_vec = try!(self.stream.read_exact(MaxGroupNameLength + 16));
        let is_correct_endianness = same_endianness(bytes_to_int(header_vec.slice(0, 4)));

        let svc_type = match (is_correct_endianness, bytes_to_int(header_vec.slice(0, 4))) {
            (true, correct) => correct,
            (false, incorrect) => flip_endianness(incorrect)
        };

        let sender = try!(
            ISO_8859_1.decode(header_vec.slice(4, 36), DecodeStrict).map_err(|error| IoError {
                kind: OtherIoError,
                desc: "Decoding sender name failed",
                detail: Some(String::from_str(error.as_slice()))
            })
        );

        let num_groups = match (is_correct_endianness, bytes_to_int(header_vec.slice(36, 40))) {
            (true, correct) => correct,
            (false, incorrect) => flip_endianness(incorrect)
        };
        let data_len = match (is_correct_endianness, bytes_to_int(header_vec.slice(44, 48))) {
            (true, correct) => correct,
            (false, incorrect) => flip_endianness(incorrect)
        };

        // Groups format (sizes in bytes):
        //   groups: num_groups
        let groups_vec =
            try!(self.stream.read_exact(MaxGroupNameLength * num_groups as uint));
        let mut groups = Vec::new();

        for n in range(0, num_groups) {
            let i: uint = n as uint * MaxGroupNameLength;
            let group = try!(
                ISO_8859_1.decode(groups_vec.slice(i, i + MaxGroupNameLength), DecodeStrict)
                    .map_err(|error| IoError {
                        kind: OtherIoError,
                        desc: "Decoding group name failed",
                        detail: Some(String::from_str(error.as_slice()))
                    }));
            groups.push(group);
        }

        // Data format (sizes in bytes):
        //   data: data_len
        let data_vec = try!(self.stream.read_exact(data_len as uint));

        Ok(SpreadMessage {
            service_type: svc_type as u32,
            groups: groups,
            sender: sender,
            data: data_vec
        })
    }
}
<|endoftext|>


<commit_before>extern crate rustc_serialize;
extern crate time;
extern crate tiny_http;
extern crate url;

pub use assets::match_assets;
pub use log::LogEntry;

use std::str::FromStr;
use std::sync::Mutex;

pub mod input;

mod assets;
mod log;
mod router;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RouteError {
    /// Couldn't find a way to handle this request.
    NoRouteFound,

    WrongInput,
}

pub struct Server {
    #[inline]
    server: tiny_http::Server,
}

impl Server {
    pub fn start() -> Server {
        Server {
            server: tiny_http::ServerBuilder::new().with_port(8000).build().unwrap(),
        }
    }
}

impl IntoIterator for Server {
    type Item = Request;
    type IntoIter = IncomingRequests;

    #[inline]
    fn into_iter(self) -> IncomingRequests {
        IncomingRequests { server: self.server }
    }
}

pub struct IncomingRequests {
    server: tiny_http::Server,
}

impl Iterator for IncomingRequests {
    type Item = Request;

    #[inline]
    fn next(&mut self) -> Option<Request> {
        let request = self.server.recv().unwrap();

        Some(Request {
            url: request.url().to_owned(),
            request: Mutex::new(Some(request)),
            data: Mutex::new(None),
        })
    }
}

/// Represents a request made by the client.
pub struct Request {
    url: String,
    request: Mutex<Option<tiny_http::Request>>,     // TODO: when Mutex gets "into_inner", remove the Option
    data: Mutex<Option<Vec<u8>>>,
}

impl Request {
    /// Returns the URL requested by the client. It is not decoded and thus can contain `%20` or
    /// other characters.
    #[inline]
    pub fn url(&self) -> &str {
        &self.url
    }

    /// Returns the value of a header of the request.
    #[inline]
    pub fn header(&self, key: &str) -> Option<String> {
        self.request.lock().unwrap().as_ref().unwrap().headers().iter()
                    .find(|h| h.field.as_str() == key).map(|h| h.value.as_str().to_owned())
    }

    /// Returns the data of the request.
    pub fn data(&self) -> Vec<u8> {
        let mut data = self.data.lock().unwrap();

        if let Some(ref mut data) = *data {
            return data.clone();
        }

        let mut read = Vec::new();
        let _ = self.request.lock().unwrap().as_mut().unwrap()
                            .as_reader().read_to_end(&mut read);
        let read_clone = read.clone();
        *data = Some(read);
        read_clone
    }

    /// Consumes the `Request` and sends the response to the client.
    #[inline]
    pub fn respond(self, response: Response) {
        self.request.lock().unwrap().take().unwrap().respond(response.response)
    }

    /// Utility function similar to `respond`, but builds the "default" response corresponding to
    /// the `RouteError`.
    #[inline]
    pub fn respond_to_error(self, err: &RouteError) {
        let response = match err {
            &RouteError::NoRouteFound => tiny_http::Response::empty(404),
            &RouteError::WrongInput => tiny_http::Response::empty(400),
        };

        self.request.lock().unwrap().take().unwrap().respond(response);
    }
}

/// Contains a prototype of a response. The response is only sent when you call `Request::respond`.
pub struct Response {
    response: tiny_http::ResponseBox,
}

impl Response {
    /// Builds a `Response` that redirects the user to another URL.
    #[inline]
    pub fn redirect(target: &str) -> Response {
        let response = tiny_http::Response::empty(303);
        // TODO: slow \|/
        let response = response.with_header(tiny_http::Header::from_str(&format!("Location: {}", target)).unwrap());

        Response { response: response.boxed() }
    }

    /// Builds a `Response` that outputs HTML.
    #[inline]
    pub fn html(content: &[u8]) -> Response {
        let response = tiny_http::Response::from_data(content);
        // TODO: slow \|/
        let response = response.with_header(tiny_http::Header::from_str("Content-Type: text/html; charset=utf8").unwrap());

        Response { response: response.boxed() }
    }
}
<commit_msg>Add Response::json<commit_after>extern crate rustc_serialize;
extern crate time;
extern crate tiny_http;
extern crate url;

pub use assets::match_assets;
pub use log::LogEntry;

use std::str::FromStr;
use std::sync::Mutex;

pub mod input;

mod assets;
mod log;
mod router;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RouteError {
    /// Couldn't find a way to handle this request.
    NoRouteFound,

    WrongInput,
}

pub struct Server {
    #[inline]
    server: tiny_http::Server,
}

impl Server {
    pub fn start() -> Server {
        Server {
            server: tiny_http::ServerBuilder::new().with_port(8000).build().unwrap(),
        }
    }
}

impl IntoIterator for Server {
    type Item = Request;
    type IntoIter = IncomingRequests;

    #[inline]
    fn into_iter(self) -> IncomingRequests {
        IncomingRequests { server: self.server }
    }
}

pub struct IncomingRequests {
    server: tiny_http::Server,
}

impl Iterator for IncomingRequests {
    type Item = Request;

    #[inline]
    fn next(&mut self) -> Option<Request> {
        let request = self.server.recv().unwrap();

        Some(Request {
            url: request.url().to_owned(),
            request: Mutex::new(Some(request)),
            data: Mutex::new(None),
        })
    }
}

/// Represents a request made by the client.
pub struct Request {
    url: String,
    request: Mutex<Option<tiny_http::Request>>,     // TODO: when Mutex gets "into_inner", remove the Option
    data: Mutex<Option<Vec<u8>>>,
}

impl Request {
    /// Returns the URL requested by the client. It is not decoded and thus can contain `%20` or
    /// other characters.
    #[inline]
    pub fn url(&self) -> &str {
        &self.url
    }

    /// Returns the value of a header of the request.
    #[inline]
    pub fn header(&self, key: &str) -> Option<String> {
        self.request.lock().unwrap().as_ref().unwrap().headers().iter()
                    .find(|h| h.field.as_str() == key).map(|h| h.value.as_str().to_owned())
    }

    /// Returns the data of the request.
    pub fn data(&self) -> Vec<u8> {
        let mut data = self.data.lock().unwrap();

        if let Some(ref mut data) = *data {
            return data.clone();
        }

        let mut read = Vec::new();
        let _ = self.request.lock().unwrap().as_mut().unwrap()
                            .as_reader().read_to_end(&mut read);
        let read_clone = read.clone();
        *data = Some(read);
        read_clone
    }

    /// Consumes the `Request` and sends the response to the client.
    #[inline]
    pub fn respond(self, response: Response) {
        self.request.lock().unwrap().take().unwrap().respond(response.response)
    }

    /// Utility function similar to `respond`, but builds the "default" response corresponding to
    /// the `RouteError`.
    #[inline]
    pub fn respond_to_error(self, err: &RouteError) {
        let response = match err {
            &RouteError::NoRouteFound => tiny_http::Response::empty(404),
            &RouteError::WrongInput => tiny_http::Response::empty(400),
        };

        self.request.lock().unwrap().take().unwrap().respond(response);
    }
}

/// Contains a prototype of a response. The response is only sent when you call `Request::respond`.
pub struct Response {
    response: tiny_http::ResponseBox,
}

impl Response {
    /// Builds a `Response` that redirects the user to another URL.
    #[inline]
    pub fn redirect(target: &str) -> Response {
        let response = tiny_http::Response::empty(303);
        // TODO: slow \|/
        let response = response.with_header(tiny_http::Header::from_str(&format!("Location: {}", target)).unwrap());

        Response { response: response.boxed() }
    }

    /// Builds a `Response` that outputs HTML.
    #[inline]
    pub fn html(content: &[u8]) -> Response {
        let response = tiny_http::Response::from_data(content);
        // TODO: slow \|/
        let response = response.with_header(tiny_http::Header::from_str("Content-Type: text/html; charset=utf8").unwrap());

        Response { response: response.boxed() }
    }

    /// Builds a `Response` that outputs JSON.
    #[inline]
    pub fn json<T>(content: &T) -> Response where T: rustc_serialize::Encodable {
        let data = rustc_serialize::json::encode(content).unwrap();
        let response = tiny_http::Response::from_string(data);
        // TODO: slow \|/
        let response = response.with_header(tiny_http::Header::from_str("Content-Type: application/json").unwrap());

        Response { response: response.boxed() }
    }
}
<|endoftext|>