<commit_before>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, BlankRecord};

named!(#[doc = "Will parse data from a FITS file into a `Fits` structure"], pub fits<&[u8], Fits>,
       do_parse!(
           ph: primary_header >>
               many0!(take!(2880)) >>
               (Fits::new(ph))
       ));

named!(primary_header<&[u8], PrimaryHeader>,
       do_parse!(
           records: many0!(keyword_record) >>
               end_record >>
               many0!(blank_record) >>
               (PrimaryHeader::new(records))
       ));

named!(keyword_record<&[u8], KeywordRecord>,
       do_parse!(
           key: keyword  >>
               tag!("= ") >>
           vc: valuecomment >>
               (KeywordRecord::new(key, vc.0, vc.1))
       ));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(valuecomment<&[u8], (&str, Option<&str>)>,
       flat_map!(
           take!(70),
           pair!(
               value,
               opt!(comment)
           )));

named!(value<&[u8], &str>,
       map_res!(
           is_not!("/"), // TODO Differentiate on the possible value types
           str::from_utf8
       )
);

named!(comment<&[u8], &str>,
       map_res!(
           do_parse!(
               tag!("/") >>
                   comment: take_while!(is_comment_character) >>
                   (comment)
           ),
           str::from_utf8
       ));

fn is_comment_character(chr: u8) -> bool {
    32u8 <= chr && chr <= 126u8
}

named!(end_record<&[u8], Keyword>,
       map!(
           flat_map!(
               take!(80),
               pair!(tag!("END"), many0!(tag!(" ")))
           ),
           |_| { Keyword::END }
       ));

named!(blank_record<&[u8], BlankRecord>,
       map!(
           count!(tag!(" "), 80),
           |_| { BlankRecord }
       ));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, BlankRecord};
    use super::{fits, primary_header, keyword_record, keyword, valuecomment, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, f) => {
                assert_eq!(f, Fits::new(long_cadence_header()));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, h) => assert_eq!(h, long_cadence_header()),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    fn long_cadence_header<'a>() -> PrimaryHeader<'a> {
        PrimaryHeader::new(vec!(
            KeywordRecord::new(Keyword::SIMPLE, "                   T ",
                               Option::Some(" conforms to FITS standards                     ")),
            KeywordRecord::new(Keyword::BITPIX, "                   8 ",
                               Option::Some(" array data type                                ")),
            KeywordRecord::new(Keyword::NAXIS, "                   0 ",
                               Option::Some(" number of array dimensions                     ")),
            KeywordRecord::new(Keyword::EXTEND, "                   T ",
                               Option::Some(" file contains extensions                       ")),
            KeywordRecord::new(Keyword::NEXTEND, "                   2 ",
                               Option::Some(" number of standard extensions                  ")),
            KeywordRecord::new(Keyword::EXTNAME, "'PRIMARY '           ",
                               Option::Some(" name of extension                              ")),
            KeywordRecord::new(Keyword::EXTVER, "                   1 ",
                               Option::Some(" extension version number (not format version)  ")),
            KeywordRecord::new(Keyword::ORIGIN, "'Unofficial data product' ",
                               Option::Some(" institution responsible for creating this ")),
            KeywordRecord::new(Keyword::DATE, "'2017-03-08'         ",
                               Option::Some(" file creation date.                            ")),
            KeywordRecord::new(Keyword::CREATOR, "'kadenza '           ",
                               Option::Some(" pipeline job and program u                     ")),
            KeywordRecord::new(Keyword::PROCVER, "'2.1.dev '           ",
                               Option::Some(" SW version                                     ")),
            KeywordRecord::new(Keyword::FILEVER, "'0.0     '           ",
                               Option::Some(" file format version                            ")),
            KeywordRecord::new(Keyword::TIMVERSN, "'' ",
                               Option::Some(" OGIP memo number for file format                                 ")),
            KeywordRecord::new(Keyword::TELESCOP, "'Kepler  '           ",
                               Option::Some(" telescope                                      ")),
            KeywordRecord::new(Keyword::INSTRUME, "'Kepler Photometer'  ",
                               Option::Some(" detector type                                  ")),
            KeywordRecord::new(Keyword::OBJECT, "'EPIC 200164267'     ",
                               Option::Some(" string version of target id                    ")),
            KeywordRecord::new(Keyword::KEPLERID, "           200164267 ",
                               Option::Some(" unique Kepler target identifier                ")),
            KeywordRecord::new(Keyword::CHANNEL, "                  68 ",
                               Option::Some(" CCD channel                                    ")),
            KeywordRecord::new(Keyword::MODULE, "                  19 ",
                               Option::Some(" CCD module                                     ")),
            KeywordRecord::new(Keyword::OUTPUT, "                   4 ",
                               Option::Some(" CCD output                                     ")),
            KeywordRecord::new(Keyword::CAMPAIGN, "'' ",
                               Option::Some(" Observing campaign number                                        ")),
            KeywordRecord::new(Keyword::DATA_REL, "'' ",
                               Option::Some(" data release version number                                      ")),
            KeywordRecord::new(Keyword::OBSMODE, "'long cadence'       ",
                               Option::Some(" observing mode                                 ")),
            KeywordRecord::new(Keyword::MISSION, "'K2      '           ",
                               Option::Some(" Mission name                                   ")),
            KeywordRecord::new(Keyword::TTABLEID, "'' ",
                               Option::Some(" target table id                                                  ")),
            KeywordRecord::new(Keyword::RADESYS, "'ICRS    '           ",
                               Option::Some(" reference frame of celestial coordinates       ")),
            KeywordRecord::new(Keyword::RA_OBJ, "'' ",
                               Option::Some(" [deg] right ascension                                            ")),
            KeywordRecord::new(Keyword::DEC_OBJ, "'' ",
                               Option::Some(" [deg] declination                                                ")),
            KeywordRecord::new(Keyword::EQUINOX, "              2000.0 ",
                               Option::Some(" equinox of celestial coordinate system         ")),
            KeywordRecord::new(Keyword::PMRA, " ",
                               Option::Some(" [arcsec/yr] RA proper motion                                       ")),
            KeywordRecord::new(Keyword::PMDEC, " ",
                               Option::Some(" [arcsec/yr] Dec proper motion                                      ")),
            KeywordRecord::new(Keyword::PMTOTAL, " ",
                               Option::Some(" [arcsec/yr] total proper motion                                    ")),
            KeywordRecord::new(Keyword::PARALLAX, " ",
                               Option::Some(" [arcsec] parallax                                                  ")),
            KeywordRecord::new(Keyword::GLON, " ",
                               Option::Some(" [deg] galactic longitude                                           ")),
            KeywordRecord::new(Keyword::GLAT, " ",
                               Option::Some(" [deg] galactic latitude                                            ")),
            KeywordRecord::new(Keyword::GMAG, " ",
                               Option::Some(" [mag] SDSS g band magnitude                                        ")),
            KeywordRecord::new(Keyword::RMAG, " ",
                               Option::Some(" [mag] SDSS r band magnitude                                        ")),
            KeywordRecord::new(Keyword::IMAG, " ",
                               Option::Some(" [mag] SDSS i band magnitude                                        ")),
            KeywordRecord::new(Keyword::ZMAG, " ",
                               Option::Some(" [mag] SDSS z band magnitude                                        ")),
            KeywordRecord::new(Keyword::JMAG, " ",
                               Option::Some(" [mag] J band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::HMAG, " ",
                               Option::Some(" [mag] H band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KMAG, " ",
                               Option::Some(" [mag] K band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KEPMAG, " ",
                               Option::Some(" [mag] Kepler magnitude (Kp)                                        ")),
            KeywordRecord::new(Keyword::GRCOLOR, " ",
                               Option::Some(" [mag] (g-r) color, SDSS bands                                      ")),
            KeywordRecord::new(Keyword::JKCOLOR, " ",
                               Option::Some(" [mag] (J-K) color, 2MASS bands                                     ")),
            KeywordRecord::new(Keyword::GKCOLOR, " ",
                               Option::Some(" [mag] (g-K) color, SDSS g - 2MASS K                                ")),
            KeywordRecord::new(Keyword::TEFF, " ",
                               Option::Some(" [K] Effective temperature                                          ")),
            KeywordRecord::new(Keyword::LOGG, " ",
                               Option::Some(" [cm/s2] log10 surface gravity                                      ")),
            KeywordRecord::new(Keyword::FEH, " ",
                               Option::Some(" [log10([Fe/H])]  metallicity                                       ")),
            KeywordRecord::new(Keyword::EBMINUSV, " ",
                               Option::Some(" [mag] E(B-V) reddening                                             ")),
            KeywordRecord::new(Keyword::AV, " ",
                               Option::Some(" [mag] A_v extinction                                               ")),
            KeywordRecord::new(Keyword::RADIUS, " ",
                               Option::Some(" [solar radii] stellar radius                                       ")),
            KeywordRecord::new(Keyword::TMINDEX, " ",
                               Option::Some(" unique 2MASS catalog ID                                            ")),
            KeywordRecord::new(Keyword::CHECKSUM, "'7k7A7h637h697h69'   ",
                               Option::Some(" HDU checksum updated 2017-03-08T02:47:56       ")),
            KeywordRecord::new(Keyword::DATASUM, "'0       '           ",
                               Option::Some(" data unit checksum updated 2017-03-08T02:47:56 ")),
        ))
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => {
                assert_eq!(k, KeywordRecord::new(
                    Keyword::OBJECT,
                    "'EPIC 200164267'     ",
                    Option::Some(" string version of target id                    ")
                ))
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn valuecomment_should_parse_a_valuecomment(){
        let data = "'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = valuecomment(data);

        match result {
            IResult::Done(_, (value, comment)) => {
                assert_eq!(value, "'EPIC 200164267'     ");
                assert_eq!(comment, Option::Some(" string version of target id                    "));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::END),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_, record) => assert_eq!(record, BlankRecord),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<commit_msg>Parse a character_string<commit_after>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, Value, BlankRecord};

named!(#[doc = "Will parse data from a FITS file into a `Fits` structure"], pub fits<&[u8], Fits>,
       do_parse!(
           ph: primary_header >>
               many0!(take!(2880)) >>
               (Fits::new(ph))
       ));

named!(primary_header<&[u8], PrimaryHeader>,
       do_parse!(
           records: many0!(keyword_record) >>
               end_record >>
               many0!(blank_record) >>
               (PrimaryHeader::new(records))
       ));

named!(keyword_record<&[u8], KeywordRecord>,
       do_parse!(
           key: keyword  >>
               tag!("= ") >>
           vc: valuecomment >>
               (KeywordRecord::new(key, vc.0, vc.1))
       ));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(valuecomment<&[u8], (&str, Option<&str>)>,
       flat_map!(
           take!(70),
           pair!(
               value,
               opt!(comment)
           )));

named!(value<&[u8], &str>,
       map_res!(
           is_not!("/"), // TODO Differentiate on the possible value types
           str::from_utf8
       )
);

named!(character_string<&[u8], Value>,
       map!(
           map_res!(
               delimited!(
                   tag!("'"),
                   take_while!(is_character_string_character),
                   tag!("'")
               ),
               str::from_utf8
           ),
           Value::CharacterString
       ));

fn is_character_string_character(chr: u8) -> bool {
    is_restricted_ascii(chr) && chr != 39
}

named!(comment<&[u8], &str>,
       map_res!(
           do_parse!(
               tag!("/") >>
                   comment: take_while!(is_comment_character) >>
                   (comment)
           ),
           str::from_utf8
       ));

fn is_comment_character(chr: u8) -> bool {
    is_restricted_ascii(chr)
}

fn is_restricted_ascii(chr: u8) -> bool {
    32u8 <= chr && chr <= 126u8
}

named!(end_record<&[u8], Keyword>,
       map!(
           flat_map!(
               take!(80),
               pair!(tag!("END"), many0!(tag!(" ")))
           ),
           |_| { Keyword::END }
       ));

named!(blank_record<&[u8], BlankRecord>,
       map!(
           count!(tag!(" "), 80),
           |_| { BlankRecord }
       ));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::{Fits, PrimaryHeader, KeywordRecord, Keyword, Value, BlankRecord};
    use super::{fits, primary_header, keyword_record, keyword, valuecomment, character_string, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, f) => {
                assert_eq!(f, Fits::new(long_cadence_header()));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, h) => assert_eq!(h, long_cadence_header()),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    fn long_cadence_header<'a>() -> PrimaryHeader<'a> {
        PrimaryHeader::new(vec!(
            KeywordRecord::new(Keyword::SIMPLE, "                   T ",
                               Option::Some(" conforms to FITS standards                     ")),
            KeywordRecord::new(Keyword::BITPIX, "                   8 ",
                               Option::Some(" array data type                                ")),
            KeywordRecord::new(Keyword::NAXIS, "                   0 ",
                               Option::Some(" number of array dimensions                     ")),
            KeywordRecord::new(Keyword::EXTEND, "                   T ",
                               Option::Some(" file contains extensions                       ")),
            KeywordRecord::new(Keyword::NEXTEND, "                   2 ",
                               Option::Some(" number of standard extensions                  ")),
            KeywordRecord::new(Keyword::EXTNAME, "'PRIMARY '           ",
                               Option::Some(" name of extension                              ")),
            KeywordRecord::new(Keyword::EXTVER, "                   1 ",
                               Option::Some(" extension version number (not format version)  ")),
            KeywordRecord::new(Keyword::ORIGIN, "'Unofficial data product' ",
                               Option::Some(" institution responsible for creating this ")),
            KeywordRecord::new(Keyword::DATE, "'2017-03-08'         ",
                               Option::Some(" file creation date.                            ")),
            KeywordRecord::new(Keyword::CREATOR, "'kadenza '           ",
                               Option::Some(" pipeline job and program u                     ")),
            KeywordRecord::new(Keyword::PROCVER, "'2.1.dev '           ",
                               Option::Some(" SW version                                     ")),
            KeywordRecord::new(Keyword::FILEVER, "'0.0     '           ",
                               Option::Some(" file format version                            ")),
            KeywordRecord::new(Keyword::TIMVERSN, "'' ",
                               Option::Some(" OGIP memo number for file format                                 ")),
            KeywordRecord::new(Keyword::TELESCOP, "'Kepler  '           ",
                               Option::Some(" telescope                                      ")),
            KeywordRecord::new(Keyword::INSTRUME, "'Kepler Photometer'  ",
                               Option::Some(" detector type                                  ")),
            KeywordRecord::new(Keyword::OBJECT, "'EPIC 200164267'     ",
                               Option::Some(" string version of target id                    ")),
            KeywordRecord::new(Keyword::KEPLERID, "           200164267 ",
                               Option::Some(" unique Kepler target identifier                ")),
            KeywordRecord::new(Keyword::CHANNEL, "                  68 ",
                               Option::Some(" CCD channel                                    ")),
            KeywordRecord::new(Keyword::MODULE, "                  19 ",
                               Option::Some(" CCD module                                     ")),
            KeywordRecord::new(Keyword::OUTPUT, "                   4 ",
                               Option::Some(" CCD output                                     ")),
            KeywordRecord::new(Keyword::CAMPAIGN, "'' ",
                               Option::Some(" Observing campaign number                                        ")),
            KeywordRecord::new(Keyword::DATA_REL, "'' ",
                               Option::Some(" data release version number                                      ")),
            KeywordRecord::new(Keyword::OBSMODE, "'long cadence'       ",
                               Option::Some(" observing mode                                 ")),
            KeywordRecord::new(Keyword::MISSION, "'K2      '           ",
                               Option::Some(" Mission name                                   ")),
            KeywordRecord::new(Keyword::TTABLEID, "'' ",
                               Option::Some(" target table id                                                  ")),
            KeywordRecord::new(Keyword::RADESYS, "'ICRS    '           ",
                               Option::Some(" reference frame of celestial coordinates       ")),
            KeywordRecord::new(Keyword::RA_OBJ, "'' ",
                               Option::Some(" [deg] right ascension                                            ")),
            KeywordRecord::new(Keyword::DEC_OBJ, "'' ",
                               Option::Some(" [deg] declination                                                ")),
            KeywordRecord::new(Keyword::EQUINOX, "              2000.0 ",
                               Option::Some(" equinox of celestial coordinate system         ")),
            KeywordRecord::new(Keyword::PMRA, " ",
                               Option::Some(" [arcsec/yr] RA proper motion                                       ")),
            KeywordRecord::new(Keyword::PMDEC, " ",
                               Option::Some(" [arcsec/yr] Dec proper motion                                      ")),
            KeywordRecord::new(Keyword::PMTOTAL, " ",
                               Option::Some(" [arcsec/yr] total proper motion                                    ")),
            KeywordRecord::new(Keyword::PARALLAX, " ",
                               Option::Some(" [arcsec] parallax                                                  ")),
            KeywordRecord::new(Keyword::GLON, " ",
                               Option::Some(" [deg] galactic longitude                                           ")),
            KeywordRecord::new(Keyword::GLAT, " ",
                               Option::Some(" [deg] galactic latitude                                            ")),
            KeywordRecord::new(Keyword::GMAG, " ",
                               Option::Some(" [mag] SDSS g band magnitude                                        ")),
            KeywordRecord::new(Keyword::RMAG, " ",
                               Option::Some(" [mag] SDSS r band magnitude                                        ")),
            KeywordRecord::new(Keyword::IMAG, " ",
                               Option::Some(" [mag] SDSS i band magnitude                                        ")),
            KeywordRecord::new(Keyword::ZMAG, " ",
                               Option::Some(" [mag] SDSS z band magnitude                                        ")),
            KeywordRecord::new(Keyword::JMAG, " ",
                               Option::Some(" [mag] J band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::HMAG, " ",
                               Option::Some(" [mag] H band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KMAG, " ",
                               Option::Some(" [mag] K band magnitude from 2MASS                                  ")),
            KeywordRecord::new(Keyword::KEPMAG, " ",
                               Option::Some(" [mag] Kepler magnitude (Kp)                                        ")),
            KeywordRecord::new(Keyword::GRCOLOR, " ",
                               Option::Some(" [mag] (g-r) color, SDSS bands                                      ")),
            KeywordRecord::new(Keyword::JKCOLOR, " ",
                               Option::Some(" [mag] (J-K) color, 2MASS bands                                     ")),
            KeywordRecord::new(Keyword::GKCOLOR, " ",
                               Option::Some(" [mag] (g-K) color, SDSS g - 2MASS K                                ")),
            KeywordRecord::new(Keyword::TEFF, " ",
                               Option::Some(" [K] Effective temperature                                          ")),
            KeywordRecord::new(Keyword::LOGG, " ",
                               Option::Some(" [cm/s2] log10 surface gravity                                      ")),
            KeywordRecord::new(Keyword::FEH, " ",
                               Option::Some(" [log10([Fe/H])]  metallicity                                       ")),
            KeywordRecord::new(Keyword::EBMINUSV, " ",
                               Option::Some(" [mag] E(B-V) reddening                                             ")),
            KeywordRecord::new(Keyword::AV, " ",
                               Option::Some(" [mag] A_v extinction                                               ")),
            KeywordRecord::new(Keyword::RADIUS, " ",
                               Option::Some(" [solar radii] stellar radius                                       ")),
            KeywordRecord::new(Keyword::TMINDEX, " ",
                               Option::Some(" unique 2MASS catalog ID                                            ")),
            KeywordRecord::new(Keyword::CHECKSUM, "'7k7A7h637h697h69'   ",
                               Option::Some(" HDU checksum updated 2017-03-08T02:47:56       ")),
            KeywordRecord::new(Keyword::DATASUM, "'0       '           ",
                               Option::Some(" data unit checksum updated 2017-03-08T02:47:56 ")),
        ))
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => {
                assert_eq!(k, KeywordRecord::new(
                    Keyword::OBJECT,
                    "'EPIC 200164267'     ",
                    Option::Some(" string version of target id                    ")
                ))
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn valuecomment_should_parse_a_valuecomment(){
        let data = "'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = valuecomment(data);

        match result {
            IResult::Done(_, (value, comment)) => {
                assert_eq!(value, "'EPIC 200164267'     ");
                assert_eq!(comment, Option::Some(" string version of target id                    "));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn character_string_should_parse_an_quote_delimited_string(){
        let data = "'EPIC 200164267'"
            .as_bytes();

        let result = character_string(data);

        match result {
            IResult::Done(_, value) => {
                assert_eq!(value, Value::CharacterString("EPIC 200164267"));
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::END),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_, record) => assert_eq!(record, BlankRecord),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<|endoftext|>


<commit_before>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::{Keyword, BlankRecord};

named!(fits<&[u8], ((Vec<Keyword>, Keyword, Vec<BlankRecord>), Vec<&[u8]>) >,
       pair!(primary_header, many0!( take!(2880) )));

named!(primary_header<&[u8], (Vec<Keyword>, Keyword, Vec<BlankRecord>)>,
       tuple!(
           many0!(keyword_record),
           end_record,
           many0!(blank_record)
       ));

named!(keyword_record<&[u8], Keyword>,
       map!( // TODO should use map_res!
           tuple!(
               take!(8),
               tag!("="),
               take!(71)
           ), |(slice, _, _) : (&[u8], &[u8], &[u8])| {
               let k: Keyword = match str::from_utf8(slice) {
                   Ok(s) => {
                       match Keyword::from_str(s) {
                           Ok(keyword) => keyword,
                           Err(_) => Keyword::SIMPLE // TODO this is not correct
                       }
                   }
                   Err(_) => Keyword::SIMPLE // TODO this is not correct
               };
               println!("{:?}", k);
               k
           }));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(end_record<&[u8], Keyword>,
       map!(
           pair!(tag!("END"), count!(tag!(" "), 77)),
           |_| { Keyword::END }
       ));

named!(blank_record<&[u8], BlankRecord>,
       map!(
           count!(tag!(" "), 80),
           |_| { BlankRecord }
       ));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::{Keyword, BlankRecord};
    use super::{fits, primary_header, keyword_record, keyword, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, (header, blocks)) => {
                assert_eq!(blocks.len(), 3675);
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, _) => assert!(true),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => assert_eq!(k, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::END),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_, record) => assert_eq!(record, BlankRecord),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<commit_msg>Test for number of headers<commit_after>//! The parser module is responsible for parsing FITS files.

use std::str;
use std::str::FromStr;
use super::types::{Keyword, BlankRecord};

named!(fits<&[u8], ((Vec<Keyword>, Keyword, Vec<BlankRecord>), Vec<&[u8]>) >,
       pair!(primary_header, many0!( take!(2880) )));

named!(primary_header<&[u8], (Vec<Keyword>, Keyword, Vec<BlankRecord>)>,
       tuple!(
           many0!(keyword_record),
           end_record,
           many0!(blank_record)
       ));

named!(keyword_record<&[u8], Keyword>,
       map!( // TODO should use map_res!
           tuple!(
               take!(8),
               tag!("="),
               take!(71)
           ), |(slice, _, _) : (&[u8], &[u8], &[u8])| {
               let k: Keyword = match str::from_utf8(slice) {
                   Ok(s) => {
                       match Keyword::from_str(s) {
                           Ok(keyword) => keyword,
                           Err(_) => Keyword::SIMPLE // TODO this is not correct
                       }
                   }
                   Err(_) => Keyword::SIMPLE // TODO this is not correct
               };
               println!("{:?}", k);
               k
           }));

named!(keyword<&[u8], Keyword>,
       map_res!(
           map_res!(
               take!(8),
               str::from_utf8),
           Keyword::from_str
       ));

named!(end_record<&[u8], Keyword>,
       map!(
           pair!(tag!("END"), count!(tag!(" "), 77)),
           |_| { Keyword::END }
       ));

named!(blank_record<&[u8], BlankRecord>,
       map!(
           count!(tag!(" "), 80),
           |_| { BlankRecord }
       ));

#[cfg(test)]
mod tests {
    use nom::{IResult};
    use super::super::types::{Keyword, BlankRecord};
    use super::{fits, primary_header, keyword_record, keyword, end_record, blank_record};

    #[test]
    fn it_should_parse_a_fits_file(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = fits(data);

        match result {
            IResult::Done(_, (header, blocks)) => {
                assert_eq!(blocks.len(), 3675);
            },
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn primary_header_should_parse_a_primary_header(){
        let data = include_bytes!("../../assets/images/k2-trappist1-unofficial-tpf-long-cadence.fits");

        let result = primary_header(&data[0..(2*2880)]);

        match result {
            IResult::Done(_, (headers, _, _)) => assert_eq!(headers.len(), 55),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_record_should_parse_a_keyword_record(){
        let data = "OBJECT  = 'EPIC 200164267'     / string version of target id                    "
            .as_bytes();

        let result = keyword_record(data);

        match result {
            IResult::Done(_,k) => assert_eq!(k, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn keyword_should_parse_a_keyword(){
        let data = "OBJECT  "
            .as_bytes();

        let result = keyword(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::OBJECT),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn end_record_should_parse_an_END_record(){
        let data = "END                                                                             "
            .as_bytes();

        let result = end_record(data);

        match result {
            IResult::Done(_, keyword) => assert_eq!(keyword, Keyword::END),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }

    #[test]
    fn blank_record_should_parse_a_BLANK_record(){
        let data = "                                                                                "
            .as_bytes();

        let result = blank_record(data);

        match result {
            IResult::Done(_, record) => assert_eq!(record, BlankRecord),
            IResult::Error(_) => panic!("Did not expect an error"),
            IResult::Incomplete(_) => panic!("Did not expect to be incomplete")
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

macro_rules! assert_approx_eq {
    ($a:expr, $b:expr) => ({
        let (a, b) = (&$a, &$b);
        assert!((*a - *b).abs() < 1.0e-6,
                "{} is not approximately equal to {}", *a, *b);
    })
}

pub fn main() {
    use std::f32;
    use std::f64;

    assert_approx_eq!(64f32.sqrt(), 8f32);
    assert_approx_eq!(64f64.sqrt(), 8f64);

    assert_approx_eq!(25f32.powi(-2), 0.0016f32);
    assert_approx_eq!(23.2f64.powi(2), 538.24f64);

    assert_approx_eq!(0f32.sin(), 0f32);
    assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64);

    assert_approx_eq!(0f32.cos(), 1f32);
    assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64);

    assert_approx_eq!(25f32.powf(-2f32), 0.0016f32);
    assert_approx_eq!(400f64.powf(0.5f64), 20f64);

    assert_approx_eq!((1f32.exp() - f32::consts::E).abs(), 0f32);
    assert_approx_eq!(1f64.exp(), f64::consts::E);

    assert_approx_eq!(10f32.exp2(), 1024f32);
    assert_approx_eq!(50f64.exp2(), 1125899906842624f64);

    assert_approx_eq!((f32::consts::E.ln() - 1f32).abs(), 0f32);
    assert_approx_eq!(1f64.ln(), 0f64);

    assert_approx_eq!(10f32.log10(), 1f32);
    assert_approx_eq!(f64::consts::E.log10(), f64::consts::LOG10_E);

    assert_approx_eq!(8f32.log2(), 3f32);
    assert_approx_eq!(f64::consts::E.log2(), f64::consts::LOG2_E);

    assert_approx_eq!(3.0f32.mul_add(2.0f32, 5.0f32), 11.0);
    assert_eq!(0.0f32.mul_add(-2.0, f32::consts::E), f32::consts::E);
    assert_approx_eq!(3.0f64.mul_add(2.0, 5.0), 11.0);
    assert_eq!(0.0f64.mul_add(-2.0f64, f64::consts::E), f64::consts::E);

    assert_approx_eq!((-1.0f32).abs(), 1.0f32);
    assert_approx_eq!(34.2f64.abs(), 34.2f64);

    assert_approx_eq!(3.8f32.floor(), 3.0f32);
    assert_approx_eq!((-1.1f64).floor(), -2.0f64);

    assert_approx_eq!((-2.3f32).ceil(), -2.0f32);
    assert_approx_eq!(3.8f64.ceil(), 4.0f64);

    assert_approx_eq!(0.1f32.trunc(), 0.0f32);
    assert_approx_eq!((-0.1f64).trunc(), 0.0f64);

    assert_approx_eq!(27.0f32.cbrt(), 3.0f32);
    assert_approx_eq!(27.0f64.cbrt(), 3.0f64);

    assert_approx_eq!(3.0f32.hypot(4.0f32), 5.0f32);
    assert_approx_eq!(3.0f64.hypot(4.0f64), 5.0f64);

    assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);
    assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);

    assert_approx_eq!(1.0f32.cosh(), 1.54308f32);
    assert_approx_eq!(1.0f64.cosh(), 1.54308f64);

    assert_approx_eq!(1.0f32.sinh(), 1.1752012f32);
    assert_approx_eq!(1.0f64.sinh(), 1.1752012f64);

    assert_approx_eq!(1.0f32.tan(), 1.557408f32);
    assert_approx_eq!(1.0f64.tan(), 1.557408f64);

    extern {
        fn ldexp(x: f64, n: i32) -> f64;
    }
    unsafe { assert_approx_eq!(ldexp(0.65f64, 3i32), 5.2f64); }
}
<commit_msg>Add test for f32::round and f64::round<commit_after>// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

macro_rules! assert_approx_eq {
    ($a:expr, $b:expr) => ({
        let (a, b) = (&$a, &$b);
        assert!((*a - *b).abs() < 1.0e-6,
                "{} is not approximately equal to {}", *a, *b);
    })
}

pub fn main() {
    use std::f32;
    use std::f64;

    assert_approx_eq!(64f32.sqrt(), 8f32);
    assert_approx_eq!(64f64.sqrt(), 8f64);

    assert_approx_eq!(25f32.powi(-2), 0.0016f32);
    assert_approx_eq!(23.2f64.powi(2), 538.24f64);

    assert_approx_eq!(0f32.sin(), 0f32);
    assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64);

    assert_approx_eq!(0f32.cos(), 1f32);
    assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64);

    assert_approx_eq!(25f32.powf(-2f32), 0.0016f32);
    assert_approx_eq!(400f64.powf(0.5f64), 20f64);

    assert_approx_eq!((1f32.exp() - f32::consts::E).abs(), 0f32);
    assert_approx_eq!(1f64.exp(), f64::consts::E);

    assert_approx_eq!(10f32.exp2(), 1024f32);
    assert_approx_eq!(50f64.exp2(), 1125899906842624f64);

    assert_approx_eq!((f32::consts::E.ln() - 1f32).abs(), 0f32);
    assert_approx_eq!(1f64.ln(), 0f64);

    assert_approx_eq!(10f32.log10(), 1f32);
    assert_approx_eq!(f64::consts::E.log10(), f64::consts::LOG10_E);

    assert_approx_eq!(8f32.log2(), 3f32);
    assert_approx_eq!(f64::consts::E.log2(), f64::consts::LOG2_E);

    assert_approx_eq!(3.0f32.mul_add(2.0f32, 5.0f32), 11.0);
    assert_eq!(0.0f32.mul_add(-2.0, f32::consts::E), f32::consts::E);
    assert_approx_eq!(3.0f64.mul_add(2.0, 5.0), 11.0);
    assert_eq!(0.0f64.mul_add(-2.0f64, f64::consts::E), f64::consts::E);

    assert_approx_eq!((-1.0f32).abs(), 1.0f32);
    assert_approx_eq!(34.2f64.abs(), 34.2f64);

    assert_approx_eq!(3.8f32.floor(), 3.0f32);
    assert_approx_eq!((-1.1f64).floor(), -2.0f64);

    assert_approx_eq!((-2.3f32).ceil(), -2.0f32);
    assert_approx_eq!(3.8f64.ceil(), 4.0f64);

    assert_approx_eq!(0.1f32.trunc(), 0.0f32);
    assert_approx_eq!((-0.1f64).trunc(), 0.0f64);

    assert_approx_eq!(27.0f32.cbrt(), 3.0f32);
    assert_approx_eq!(27.0f64.cbrt(), 3.0f64);

    assert_approx_eq!(3.0f32.hypot(4.0f32), 5.0f32);
    assert_approx_eq!(3.0f64.hypot(4.0f64), 5.0f64);

    assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);
    assert_approx_eq!(1.0f32.atan2(2.0f32), 0.46364761f32);

    assert_approx_eq!(1.0f32.cosh(), 1.54308f32);
    assert_approx_eq!(1.0f64.cosh(), 1.54308f64);

    assert_approx_eq!(1.0f32.sinh(), 1.1752012f32);
    assert_approx_eq!(1.0f64.sinh(), 1.1752012f64);

    assert_approx_eq!(1.0f32.tan(), 1.557408f32);
    assert_approx_eq!(1.0f64.tan(), 1.557408f64);

    assert_eq!(3.3_f32.round(), 3.0);
    assert_eq!(3.3_f64.round(), 3.0);

    extern {
        fn ldexp(x: f64, n: i32) -> f64;
    }
    unsafe { assert_approx_eq!(ldexp(0.65f64, 3i32), 5.2f64); }
}
<|endoftext|>


<commit_before>//! Tests for `cargo-features` definitions.

use cargo_test_support::{project, registry};

#[cargo_test]
fn feature_required() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  consider adding `cargo-features = [\"test-dummy-unstable\"]` to the manifest
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  this Cargo does not support nightly features, but if you
  switch to nightly channel you can add
  `cargo-features = [\"test-dummy-unstable\"]` to enable this feature
",
        )
        .run();
}

#[cargo_test]
fn unknown_feature() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["foo"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  unknown cargo feature `foo`
",
        )
        .run();
}

#[cargo_test]
fn stable_feature_warns() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-stable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_stderr(
            "\
warning: the cargo feature `test-dummy-stable` has been stabilized in the 1.0 \
release and is no longer necessary to be listed in the manifest
  See https://doc.rust-lang.org/[..]cargo/ for more information about using this feature.
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn allow_features() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    // NOTE: We need to use RUSTC_BOOTSTRAP here since we also need nightly rustc

    p.cargo("-Zallow-features=test-dummy-unstable build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable,print-im-a-teapot -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stdout("im-a-teapot = true")
        .with_stderr("[FINISHED] [..]")
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: the feature `print-im-a-teapot` is not in the list of allowed features: [test-dummy-unstable]
",
        )
        .run();

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the feature `test-dummy-unstable` is not in the list of allowed features: []
",
        )
        .run();
}

#[cargo_test]
fn allow_features_to_rustc() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file(
            "src/lib.rs",
            r#"
                #![feature(test_2018_feature)]
            "#,
        )
        .build();

    // NOTE: We need to use RUSTC_BOOTSTRAP here since we also need nightly rustc

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] a [..]
error[E0725]: the feature `test_2018_feature` is not in the list of allowed features
",
        )
        .with_stderr_contains(
            "\
error: could not compile `a`
",
        )
        .run();

    p.cargo("-Zallow-features=test_2018_feature build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn allow_features_in_cfg() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file(
            ".cargo/config.toml",
            r#"
                [unstable]
                allow-features = ["test-dummy-unstable", "print-im-a-teapot"]
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    // NOTE: We need to use RUSTC_BOOTSTRAP here since we also need nightly rustc

    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("-Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stdout("im-a-teapot = true")
        .with_stderr("[FINISHED] [..]")
        .run();

    p.cargo("-Zunstable-options build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: the feature `unstable-options` is not in the list of allowed features: [print-im-a-teapot, test-dummy-unstable]
",
        )
        .run();

    // -Zallow-features overrides .cargo/config
    p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: the feature `print-im-a-teapot` is not in the list of allowed features: [test-dummy-unstable]
",
        )
        .run();

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the feature `test-dummy-unstable` is not in the list of allowed features: []
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly_in_dep() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "b"
                version = "0.0.1"
                authors = []

                [dependencies]
                a = { path = "a" }
            "#,
        )
        .file("src/lib.rs", "")
        .file(
            "a/Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("a/src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[COMPILING] b [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
[ERROR] failed to get `a` as a dependency of package `b v0.0.1 ([..])`

Caused by:
  failed to load source for dependency `a`

Caused by:
  Unable to update [..]

Caused by:
  failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn cant_publish() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn z_flags_rejected() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build -Zprint-im-a-teapot")
        .with_status(101)
        .with_stderr(
            "error: the `-Z` flag is only accepted on the nightly \
             channel of Cargo, but this is the `stable` channel\n\
             See [..]",
        )
        .run();

    p.cargo("build -Zarg")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr("error: unknown `-Z` flag specified: arg")
        .run();

    p.cargo("build -Zprint-im-a-teapot")
        .masquerade_as_nightly_cargo()
        .with_stdout("im-a-teapot = true\n")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn publish_allowed() {
    registry::init();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("publish --token sekrit")
        .masquerade_as_nightly_cargo()
        .run();
}

#[cargo_test]
fn wrong_position() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                cargo-features = ["test-dummy-unstable"]
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("check")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at [..]

Caused by:
  cargo-features = [\"test-dummy-unstable\"] was found in the wrong location, it \
  should be set at the top of Cargo.toml before any tables
",
        )
        .run();
}

#[cargo_test]
fn z_stabilized() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("check -Z cache-messages")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
warning: flag `-Z cache-messages` has been stabilized in the 1.40 release, \
  and is no longer necessary
  Message caching is now always enabled.

[CHECKING] foo [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("check -Z offline")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: flag `-Z offline` has been stabilized in the 1.36 release
  Offline mode is now available via the --offline CLI option

",
        )
        .run();
}
<commit_msg>Future-proof tests<commit_after>//! Tests for `cargo-features` definitions.

use cargo_test_support::{project, registry};

#[cargo_test]
fn feature_required() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  consider adding `cargo-features = [\"test-dummy-unstable\"]` to the manifest
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  this Cargo does not support nightly features, but if you
  switch to nightly channel you can add
  `cargo-features = [\"test-dummy-unstable\"]` to enable this feature
",
        )
        .run();
}

#[cargo_test]
fn unknown_feature() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["foo"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  unknown cargo feature `foo`
",
        )
        .run();
}

#[cargo_test]
fn stable_feature_warns() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-stable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_stderr(
            "\
warning: the cargo feature `test-dummy-stable` has been stabilized in the 1.0 \
release and is no longer necessary to be listed in the manifest
  See https://doc.rust-lang.org/[..]cargo/ for more information about using this feature.
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn allow_features() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    // NOTE: We need to use RUSTC_BOOTSTRAP here since we also need nightly rustc

    p.cargo("-Zallow-features=test-dummy-unstable build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable,print-im-a-teapot -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stdout("im-a-teapot = true")
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: the feature `print-im-a-teapot` is not in the list of allowed features: [test-dummy-unstable]
",
        )
        .run();

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the feature `test-dummy-unstable` is not in the list of allowed features: []
",
        )
        .run();
}

#[cargo_test]
fn allow_features_to_rustc() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file(
            "src/lib.rs",
            r#"
                #![feature(test_2018_feature)]
            "#,
        )
        .build();

    // NOTE: We need to use RUSTC_BOOTSTRAP here since we also need nightly rustc

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr_contains("[..]E0725[..]")
        .run();

    p.cargo("-Zallow-features=test_2018_feature build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn allow_features_in_cfg() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file(
            ".cargo/config.toml",
            r#"
                [unstable]
                allow-features = ["test-dummy-unstable", "print-im-a-teapot"]
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    // NOTE: We need to use RUSTC_BOOTSTRAP here since we also need nightly rustc

    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("-Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_stdout("im-a-teapot = true")
        .with_stderr("[FINISHED] [..]")
        .run();

    p.cargo("-Zunstable-options build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: the feature `unstable-options` is not in the list of allowed features: [print-im-a-teapot, test-dummy-unstable]
",
        )
        .run();

    // -Zallow-features overrides .cargo/config
    p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: the feature `print-im-a-teapot` is not in the list of allowed features: [test-dummy-unstable]
",
        )
        .run();

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .env("RUSTC_BOOTSTRAP", "1")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the feature `test-dummy-unstable` is not in the list of allowed features: []
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly_in_dep() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "b"
                version = "0.0.1"
                authors = []

                [dependencies]
                a = { path = "a" }
            "#,
        )
        .file("src/lib.rs", "")
        .file(
            "a/Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("a/src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[COMPILING] b [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
[ERROR] failed to get `a` as a dependency of package `b v0.0.1 ([..])`

Caused by:
  failed to load source for dependency `a`

Caused by:
  Unable to update [..]

Caused by:
  failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn cant_publish() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn z_flags_rejected() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build -Zprint-im-a-teapot")
        .with_status(101)
        .with_stderr(
            "error: the `-Z` flag is only accepted on the nightly \
             channel of Cargo, but this is the `stable` channel\n\
             See [..]",
        )
        .run();

    p.cargo("build -Zarg")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr("error: unknown `-Z` flag specified: arg")
        .run();

    p.cargo("build -Zprint-im-a-teapot")
        .masquerade_as_nightly_cargo()
        .with_stdout("im-a-teapot = true\n")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn publish_allowed() {
    registry::init();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("publish --token sekrit")
        .masquerade_as_nightly_cargo()
        .run();
}

#[cargo_test]
fn wrong_position() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                cargo-features = ["test-dummy-unstable"]
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("check")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at [..]

Caused by:
  cargo-features = [\"test-dummy-unstable\"] was found in the wrong location, it \
  should be set at the top of Cargo.toml before any tables
",
        )
        .run();
}

#[cargo_test]
fn z_stabilized() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("check -Z cache-messages")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
warning: flag `-Z cache-messages` has been stabilized in the 1.40 release, \
  and is no longer necessary
  Message caching is now always enabled.

[CHECKING] foo [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("check -Z offline")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: flag `-Z offline` has been stabilized in the 1.36 release
  Offline mode is now available via the --offline CLI option

",
        )
        .run();
}
<|endoftext|>


<commit_before>//! The SpaceAPI server struct.

use std::net::ToSocketAddrs;
use std::sync::Arc;
use std::time::Duration;

use r2d2;
use r2d2_redis::RedisConnectionManager;
use iron::Iron;
use router::Router;
use redis::{IntoConnectionInfo, ConnectionInfo};

use serde_json::Value;
use serde_json::map::Map;

mod handlers;

use crate::api;

use crate::sensors;
use crate::modifiers;
use crate::errors::SpaceapiServerError;
use crate::types::RedisPool;

pub struct SpaceapiServerBuilder {
    status: api::Status,
    redis_connection_info: Result<ConnectionInfo, SpaceapiServerError>,
    pool: Option<r2d2::Pool<r2d2_redis::RedisConnectionManager>>,
    sensor_specs: Vec<sensors::SensorSpec>,
    status_modifiers: Vec<Box<modifiers::StatusModifier>>,
}

impl SpaceapiServerBuilder {

    pub fn new(mut status: api::Status) -> SpaceapiServerBuilder {
        // Instantiate versions object
        let mut versions = Map::new();
        versions.insert("spaceapi-rs".into(), api::get_version().into());
        versions.insert("spaceapi-server-rs".into(), crate::get_version().into());

        // Add to extensions
        status.extensions.insert("versions".into(), Value::Object(versions));

        SpaceapiServerBuilder {
            status,
            redis_connection_info: Err("redis_connection_info missing".into()),
            pool: None,
            sensor_specs: vec![],
            status_modifiers: vec![],
        }
    }

    pub fn redis_connection_info<R: IntoConnectionInfo>(mut self, redis_connection_info: R) -> Self {
        self.redis_connection_info = redis_connection_info.into_connection_info().map_err(|e| e.into());
        self
    }

    pub fn redis_pool(mut self, redis_pool: r2d2::Pool<r2d2_redis::RedisConnectionManager>) -> Self {
        self.pool = Ok(redis_pool);
        self
    }

    pub fn add_status_modifier<M: modifiers::StatusModifier + 'static>(mut self, modifier: M) -> Self {
        self.status_modifiers.push(Box::new(modifier));
        self
    }

    /// Add a new sensor.
    ///
    /// The first argument is a ``api::SensorTemplate`` instance containing all static data.
    /// The second argument specifies how to get the actual sensor value from Redis.
    pub fn add_sensor<T: api::SensorTemplate + 'static>(mut self, template: T, data_key: String) -> Self {
        self.sensor_specs.push(
            sensors::SensorSpec { template: Box::new(template), data_key }
        );
        self
    }

    pub fn build(self) -> Result<SpaceapiServer, SpaceapiServerError> {
        // Log some useful debug information
        debug!("Redis connection info: {:?}", &self.redis_connection_info);

        let pool = match self.pool {
            Some(p) => p,
            None => {
                // Create redis pool
                let redis_config = r2d2::Config::builder()
                    // Provide up to 6 connections in connection pool
                    .pool_size(6)
                    // At least 1 connection must be active
                    .min_idle(Some(2))
                    // Initialize connection pool lazily. This allows the SpaceAPI
                    // server to work even without a database connection.
                    .initialization_fail_fast(false)
                    // Try to get a connection for max 1 second
                    .connection_timeout(Duration::from_secs(1))
                    // Don't log errors directly.
                    // They can get quite verbose, and we're already catching and
                    // logging the corresponding results anyways.
                    .error_handler(Box::new(r2d2::NopErrorHandler))
                    .build();
                let redis_manager = RedisConnectionManager::new(self.redis_connection_info?)?;
                r2d2::Pool::new(redis_config, redis_manager)?
            },
        };

        Ok(SpaceapiServer {
            status: self.status,
            redis_pool: pool,
            sensor_specs: Arc::new(self.sensor_specs),
            status_modifiers: self.status_modifiers,
        })
    }
}

/// A Space API server instance.
///
/// You can create a new instance using the ``new`` constructor method by
/// passing it the host, the port, the ``Status`` object and a redis connection info object.
///
/// The ``SpaceapiServer`` includes a web server through
/// [Hyper](http://hyper.rs/hyper/hyper/server/index.html). Simply call the ``serve`` method.
pub struct SpaceapiServer {
    status: api::Status,
    redis_pool: RedisPool,
    sensor_specs: sensors::SafeSensorSpecs,
    status_modifiers: Vec<Box<modifiers::StatusModifier>>,
}

impl SpaceapiServer {

    /// Create and return a Router instance.
    fn route(self) -> Router {
        let mut router = Router::new();

        router.get("/", handlers::ReadHandler::new(
            self.status.clone(), self.redis_pool.clone(),
            self.sensor_specs.clone(), self.status_modifiers), "root");

        router.put("/sensors/:sensor/", handlers::UpdateHandler::new(
            self.redis_pool.clone(), self.sensor_specs.clone()), "sensors");

        router
    }

    /// Start a HTTP server listening on ``self.host:self.port``.
    ///
    /// The call returns an `HttpResult<Listening>` object, see
    /// http://ironframework.io/doc/hyper/server/struct.Listening.html
    /// for more information.
    pub fn serve<S: ToSocketAddrs>(self, socket_addr: S) -> crate::HttpResult<crate::Listening> {
        // Launch server process
        let router = self.route();
        println!("Starting HTTP server on:");
        for a in socket_addr.to_socket_addrs()? {
            println!("\thttp://{}", a);
        }
        Iron::new(router).http(socket_addr)
    }
}
<commit_msg>Unify handling of redis connections<commit_after>//! The SpaceAPI server struct.

use std::net::ToSocketAddrs;
use std::sync::Arc;
use std::time::Duration;

use r2d2;
use r2d2_redis::RedisConnectionManager;
use iron::Iron;
use router::Router;
use redis::{IntoConnectionInfo, ConnectionInfo};

use serde_json::Value;
use serde_json::map::Map;

mod handlers;

use crate::api;

use crate::sensors;
use crate::modifiers;
use crate::errors::SpaceapiServerError;
use crate::types::RedisPool;

pub struct SpaceapiServerBuilder {
    status: api::Status,
    pool: Result<r2d2::Pool<r2d2_redis::RedisConnectionManager>, SpaceapiServerError>,
    sensor_specs: Vec<sensors::SensorSpec>,
    status_modifiers: Vec<Box<modifiers::StatusModifier>>,
}

impl SpaceapiServerBuilder {

    pub fn new(mut status: api::Status) -> SpaceapiServerBuilder {
        // Instantiate versions object
        let mut versions = Map::new();
        versions.insert("spaceapi-rs".into(), api::get_version().into());
        versions.insert("spaceapi-server-rs".into(), crate::get_version().into());

        // Add to extensions
        status.extensions.insert("versions".into(), Value::Object(versions));

        SpaceapiServerBuilder {
            status,
            pool: Err("redis_connection_info missing".into()),
            sensor_specs: vec![],
            status_modifiers: vec![],
        }
    }

    pub fn redis_connection_info<R: IntoConnectionInfo>(mut self, redis_connection_info: R) -> Self {
        let redis_connection_info  = redis_connection_info
            .into_connection_info();

        // Log some useful debug information
        debug!("Redis connection info: {:?}", &redis_connection_info);

        // Create redis pool
        let redis_config = r2d2::Config::builder()
            // Provide up to 6 connections in connection pool
            .pool_size(6)
            // At least 1 connection must be active
            .min_idle(Some(2))
            // Initialize connection pool lazily. This allows the SpaceAPI
            // server to work even without a database connection.
            .initialization_fail_fast(false)
            // Try to get a connection for max 1 second
            .connection_timeout(Duration::from_secs(1))
            // Don't log errors directly.
            // They can get quite verbose, and we're already catching and
            // logging the corresponding results anyways.
            .error_handler(Box::new(r2d2::NopErrorHandler))
            .build();
        let redis_manager = RedisConnectionManager::new(redis_connection_info.unwrap());
        self.pool = r2d2::Pool::new(redis_config, redis_manager.unwrap()).or(Err("Pool creation failed".into()));
        self
    }

    pub fn redis_pool(mut self, redis_pool: r2d2::Pool<r2d2_redis::RedisConnectionManager>) -> Self {
        self.pool = Ok(redis_pool);
        self
    }

    pub fn add_status_modifier<M: modifiers::StatusModifier + 'static>(mut self, modifier: M) -> Self {
        self.status_modifiers.push(Box::new(modifier));
        self
    }

    /// Add a new sensor.
    ///
    /// The first argument is a ``api::SensorTemplate`` instance containing all static data.
    /// The second argument specifies how to get the actual sensor value from Redis.
    pub fn add_sensor<T: api::SensorTemplate + 'static>(mut self, template: T, data_key: String) -> Self {
        self.sensor_specs.push(
            sensors::SensorSpec { template: Box::new(template), data_key }
        );
        self
    }

    pub fn build(self) -> Result<SpaceapiServer, SpaceapiServerError> {

        Ok(SpaceapiServer {
            status: self.status,
            redis_pool: self.pool.expect("Error initializing redis pool"),
            sensor_specs: Arc::new(self.sensor_specs),
            status_modifiers: self.status_modifiers,
        })
    }
}

/// A Space API server instance.
///
/// You can create a new instance using the ``new`` constructor method by
/// passing it the host, the port, the ``Status`` object and a redis connection info object.
///
/// The ``SpaceapiServer`` includes a web server through
/// [Hyper](http://hyper.rs/hyper/hyper/server/index.html). Simply call the ``serve`` method.
pub struct SpaceapiServer {
    status: api::Status,
    redis_pool: RedisPool,
    sensor_specs: sensors::SafeSensorSpecs,
    status_modifiers: Vec<Box<modifiers::StatusModifier>>,
}

impl SpaceapiServer {

    /// Create and return a Router instance.
    fn route(self) -> Router {
        let mut router = Router::new();

        router.get("/", handlers::ReadHandler::new(
            self.status.clone(), self.redis_pool.clone(),
            self.sensor_specs.clone(), self.status_modifiers), "root");

        router.put("/sensors/:sensor/", handlers::UpdateHandler::new(
            self.redis_pool.clone(), self.sensor_specs.clone()), "sensors");

        router
    }

    /// Start a HTTP server listening on ``self.host:self.port``.
    ///
    /// The call returns an `HttpResult<Listening>` object, see
    /// http://ironframework.io/doc/hyper/server/struct.Listening.html
    /// for more information.
    pub fn serve<S: ToSocketAddrs>(self, socket_addr: S) -> crate::HttpResult<crate::Listening> {
        // Launch server process
        let router = self.route();
        println!("Starting HTTP server on:");
        for a in socket_addr.to_socket_addrs()? {
            println!("\thttp://{}", a);
        }
        Iron::new(router).http(socket_addr)
    }
}
<|endoftext|>


<commit_before>//! This is a basic example of leveraging `UdpCodec` to create a simple UDP
//! client and server which speak a custom protocol.
//!
//! Here we're using the a custom codec to convert a UDP socket to a stream of
//! client messages. These messages are then processed and returned back as a
//! new message with a new destination. Overall, we then use this to construct a
//! "ping pong" pair where two sockets are sending messages back and forth.

extern crate tokio_core;
extern crate env_logger;
extern crate futures;

use std::io;
use std::net::SocketAddr;
use std::str;

use futures::{Future, Stream, Sink};
use tokio_core::net::{UdpSocket, UdpCodec};
use tokio_core::reactor::Core;

pub struct LineCodec;

impl UdpCodec for LineCodec {
    type In = (SocketAddr, Vec<u8>);
    type Out = (SocketAddr, Vec<u8>);

    fn decode(&mut self, addr: &SocketAddr, buf: &[u8]) -> io::Result<Self::In> {
        Ok((*addr, buf.to_vec()))
    }

    fn encode(&mut self,
              (addr, buf): (SocketAddr, Vec<u8>),
              into: &mut Vec<u8>) -> SocketAddr {
        into.extend(buf);
        return addr
    }
}

fn main() {
    drop(env_logger::init());

    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();

    // Bind both our sockets and then figure out what ports we got.
    let a = UdpSocket::bind(&addr, &handle).unwrap();
    let b = UdpSocket::bind(&addr, &handle).unwrap();
    let b_addr = b.local_addr().unwrap();

    // We're parsing each socket with the `LineCodec` defined above, and then we
    // `split` each codec into the sink/stream halves.
    let (a_sink, a_stream) = a.framed(LineCodec).split();
    let (b_sink, b_stream) = b.framed(LineCodec).split();

    // Start off by sending a ping from a to b, afterwards we just print out
    // what they send us and continually send pings
    // let pings = stream::iter((0..5).map(Ok));
    let a = a_sink.send((b_addr, b"PING".to_vec())).and_then(|a_sink| {
        let mut i = 0;
        let a_stream = a_stream.take(4).map(move |(addr, msg)| {
            i += 1;
            println!("[a] recv: {}", String::from_utf8_lossy(&msg));
            (addr, format!("PING {}", i).into_bytes())
        });
        a_sink.send_all(a_stream)
    });

    // The second client we have will receive the pings from `a` and then send
    // back pongs.
    let b_stream = b_stream.map(|(addr, msg)| {
        println!("[b] recv: {}", String::from_utf8_lossy(&msg));
        (addr, b"PONG".to_vec())
    });
    let b = b_sink.send_all(b_stream);

    // Spawn the sender of pongs and then wait for our pinger to finish.
    handle.spawn(b.then(|_| Ok(())));
    drop(core.run(a));
}
<commit_msg>use the 'Out' type in encode rather than copy/paste<commit_after>//! This is a basic example of leveraging `UdpCodec` to create a simple UDP
//! client and server which speak a custom protocol.
//!
//! Here we're using the a custom codec to convert a UDP socket to a stream of
//! client messages. These messages are then processed and returned back as a
//! new message with a new destination. Overall, we then use this to construct a
//! "ping pong" pair where two sockets are sending messages back and forth.

extern crate tokio_core;
extern crate env_logger;
extern crate futures;

use std::io;
use std::net::SocketAddr;
use std::str;

use futures::{Future, Stream, Sink};
use tokio_core::net::{UdpSocket, UdpCodec};
use tokio_core::reactor::Core;

pub struct LineCodec;

impl UdpCodec for LineCodec {
    type In = (SocketAddr, Vec<u8>);
    type Out = (SocketAddr, Vec<u8>);

    fn decode(&mut self, addr: &SocketAddr, buf: &[u8]) -> io::Result<Self::In> {
        Ok((*addr, buf.to_vec()))
    }

    fn encode(&mut self, (addr, buf): Self::Out, into: &mut Vec<u8>) -> SocketAddr {
        into.extend(buf);
        return addr
    }
}

fn main() {
    drop(env_logger::init());

    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();

    // Bind both our sockets and then figure out what ports we got.
    let a = UdpSocket::bind(&addr, &handle).unwrap();
    let b = UdpSocket::bind(&addr, &handle).unwrap();
    let b_addr = b.local_addr().unwrap();

    // We're parsing each socket with the `LineCodec` defined above, and then we
    // `split` each codec into the sink/stream halves.
    let (a_sink, a_stream) = a.framed(LineCodec).split();
    let (b_sink, b_stream) = b.framed(LineCodec).split();

    // Start off by sending a ping from a to b, afterwards we just print out
    // what they send us and continually send pings
    // let pings = stream::iter((0..5).map(Ok));
    let a = a_sink.send((b_addr, b"PING".to_vec())).and_then(|a_sink| {
        let mut i = 0;
        let a_stream = a_stream.take(4).map(move |(addr, msg)| {
            i += 1;
            println!("[a] recv: {}", String::from_utf8_lossy(&msg));
            (addr, format!("PING {}", i).into_bytes())
        });
        a_sink.send_all(a_stream)
    });

    // The second client we have will receive the pings from `a` and then send
    // back pongs.
    let b_stream = b_stream.map(|(addr, msg)| {
        println!("[b] recv: {}", String::from_utf8_lossy(&msg));
        (addr, b"PONG".to_vec())
    });
    let b = b_sink.send_all(b_stream);

    // Spawn the sender of pongs and then wait for our pinger to finish.
    handle.spawn(b.then(|_| Ok(())));
    drop(core.run(a));
}
<|endoftext|>