<commit_before>use std::process;
use std::fs::File;
use std::io::{Read, BufReader};

use serde_json;
use std::collections::{HashMap, HashSet};

use crate::data_types::{APIMaps, IdGeneSubsetMap, APIGeneSummary, APIAlleleDetails,
                GeneDetails, TermDetails, GenotypeDetails, ReferenceDetails,
                InteractionType, OntAnnotationMap, IdOntAnnotationDetailMap,
                TermShort, TermShortOptionMap, ChromosomeDetails,
                ReferenceShort, ReferenceShortOptionMap,
                GeneShort, GeneShortOptionMap, GeneQueryData};
use crate::web::config::{Config, TermAndName};
use crate::api::query::{SingleOrMultiAllele, QueryExpressionFilter};

use flate2::read::GzDecoder;

use crate::types::{TermId, GeneUniquename};

use pombase_rc_string::RcString;

pub struct APIData {
    maps: APIMaps,
    secondary_identifiers_map: HashMap<TermId, TermId>,
}

fn make_secondary_identifiers_map(terms: &HashMap<TermId, TermDetails>)
                                  -> HashMap<TermId, TermId>
{
    let mut ret_map = HashMap::new();

    for term_details in terms.values() {
        for secondary_identifier in &term_details.secondary_identifiers {
            ret_map.insert(secondary_identifier.clone(),
                           term_details.termid.clone());
        }
    }

    ret_map
}

fn load(config: &Config, search_maps_file_name: &str) -> APIMaps
{
    let file = match File::open(search_maps_file_name) {
        Ok(file) => file,
        Err(err) => {
            eprint!("Failed to read {}: {}\n", search_maps_file_name, err);
            process::exit(1);
        }
    };
    let reader = BufReader::new(file);

    let mut decoder = GzDecoder::new(reader);
    let mut decoded_json = String::new();
    decoder.read_to_string(&mut decoded_json).unwrap();

    let mut query_api_maps: APIMaps =
        match serde_json::from_str(&decoded_json) {
            Ok(results) => results,
            Err(err) => {
                eprint!("failed to parse {}: {}", search_maps_file_name, err);
                process::exit(1);
            },
        };

    query_api_maps
}

impl APIData {
    pub fn new_from_file(config: &Config, search_maps_file_name: &str)
               -> APIData
    {
        let maps = load(&config, search_maps_file_name);

        APIData::new(config, maps)
    }

    pub fn new(config: &Config, mut maps: APIMaps)
               ->APIData
    {
        let mut new_entries: IdGeneSubsetMap = HashMap::new();

        let prefixes_to_remove: Vec<String> =
            config.server.subsets.prefixes_to_remove
            .iter().map(|prefix| prefix.clone() + ":").collect();

        // strip prefixes and add to map
        for (subset_name, subset_details) in &maps.gene_subsets {
            for prefix in &prefixes_to_remove {
                if subset_name.starts_with(prefix) {
                    let new_subset_name = &subset_name[prefix.len()..];
                    new_entries.insert(RcString::from(new_subset_name), subset_details.clone());
                }
            }
        }

        maps.gene_subsets.extend(new_entries);

        let secondary_identifiers_map =
            make_secondary_identifiers_map(&maps.terms);

        APIData {
            secondary_identifiers_map,
            maps
        }
    }

    pub fn gene_uniquename_of_id(&self, id: &RcString) -> Option<GeneUniquename> {
        if self.maps.gene_summaries.contains_key(id) {
            Some(id.clone())
        } else {
            if let Some(gene_uniquename) =
                self.maps.gene_name_gene_map.get(id) {
                    Some(gene_uniquename.clone())
                } else {
                    let lower_case_id = id.to_lowercase();
                    for gene_uniquename in self.maps.gene_summaries.keys() {
                        if gene_uniquename.to_lowercase() == lower_case_id {
                            return Some(gene_uniquename.clone());
                        }
                    }
                    None
                }
        }
    }

    pub fn get_gene_summary(&self, gene_uniquename: &str) -> Option<&APIGeneSummary> {
        self.maps.gene_summaries.get(gene_uniquename)
    }

    pub fn get_gene_details(&self, gene_uniquename: &str) -> Option<&GeneDetails> {
        self.maps.genes.get(gene_uniquename)
    }

    pub fn get_gene_query_data(&self, gene_uniquename: &str) -> Option<&GeneQueryData> {
        self.maps.gene_query_data_map.get(gene_uniquename)
    }

    pub fn genes_of_termid(&self, term_id: &str) -> Vec<GeneUniquename> {
        match self.maps.termid_genes.get(term_id) {
            Some(gene_uniquenames) => {
                gene_uniquenames.iter().cloned().collect::<Vec<_>>()
            },
            None => vec![],
        }
    }

    pub fn genes_of_subset(&self, search_name: &str) -> Vec<GeneUniquename> {
        if search_name.starts_with('!') || search_name.ends_with('*') {
            let mut trimmed_search_name = search_name.to_owned();
            let invert_search = search_name.starts_with('!');
            let wildcard = search_name.ends_with('*');
            if invert_search {
                trimmed_search_name.remove(0);
            }
            if wildcard {
                trimmed_search_name.pop();
            }
            let mut genes = HashSet::new();
            for (subset_name, subset_details) in &self.maps.gene_subsets {
                let name_matches =
                    wildcard && subset_name.starts_with(&trimmed_search_name) ||
                    trimmed_search_name.eq(subset_name);

                if !invert_search && name_matches || invert_search && !name_matches {
                    genes.extend(subset_details.elements.iter().cloned());
                }
            }
            genes.iter().cloned().collect::<Vec<_>>()
        } else {
            match self.maps.gene_subsets.get(search_name) {
                Some(subset_details) => {
                    subset_details.elements.iter().cloned().collect::<Vec<_>>()
                },
                None => vec![],
            }
        }
    }

    pub fn genes_of_genotypes(&self, term_id: &str,
                              single_or_multi_allele: &SingleOrMultiAllele,
                              expression_filter: &Option<QueryExpressionFilter>,
                              conditions_filter: &HashSet<TermAndName>,
                              excluded_conditions_filter: &HashSet<TermAndName>)
                              -> Vec<GeneUniquename>
    {
        if let Some(annotations) = self.maps.termid_genotype_annotation.get(term_id) {
            let mut genes: HashSet<GeneUniquename> = HashSet::new();
            for annotation in annotations {

                let mut add_single = false;
                let mut add_multi = false;

                match *single_or_multi_allele {
                    SingleOrMultiAllele::Single => add_single = true,
                    SingleOrMultiAllele::Multi => add_multi = true,
                    SingleOrMultiAllele::Both => {
                        add_single = true;
                        add_multi = true;
                    },
                }

                let expression_matches =
                    |allele_details: &APIAlleleDetails| {
                        let expression = &allele_details.expression;
                        let allele_type = &allele_details.allele_type;
                        if let Some(ref expression_filter) = *expression_filter {
                            if *expression_filter == QueryExpressionFilter::Any {
                                return true;
                            }
                            if *expression_filter == QueryExpressionFilter::Null {
                                if allele_type == "deletion" {
                                    return true;
                                }
                                if let Some(ref expression) = *expression {
                                    expression == "Null"
                                } else {
                                    false
                                }
                            } else {
                                if *expression_filter == QueryExpressionFilter::WtOverexpressed {
                                    if let Some(ref expression) = *expression {
                                        expression == "Overexpression" &&
                                            allele_type == "wild_type"
                                    } else {
                                        false
                                    }
                                } else {
                                    false
                                }
                            }
                        } else {
                            true
                        }
                    };

                let condition_matches =
                    |conditions: &HashSet<TermAndName>, test_conditions_filter: &HashSet<TermAndName>| {
                        if test_conditions_filter.len() == 0 {
                            // don't filter in this case
                            return true;
                        }

                        for filter_condition in test_conditions_filter {
                            if conditions.iter().find(
                                |cond| {
                                    filter_condition.termid == cond.termid
                                })
                                .is_some() {
                                return true
                            }
                        }

                        false
                    };

                if annotation.is_multi && add_multi ||
                    !annotation.is_multi && add_single &&
                    expression_matches(&annotation.alleles[0]) &&
                    condition_matches(&annotation.conditions, conditions_filter) &&
                    (excluded_conditions_filter.len() == 0 ||
                     !condition_matches(&annotation.conditions, excluded_conditions_filter)) {
                        for allele_details in &annotation.alleles {
                            genes.insert(allele_details.gene.clone());
                        }
                }
            }
            genes.into_iter().collect::<Vec<_>>()
        } else {
            vec![]
        }
    }

    pub fn filter_genes(&self, p: & dyn Fn(&APIGeneSummary) -> bool)
                        -> Vec<GeneUniquename>
    {
        self.maps.gene_summaries.values()
            .filter(|summ| p(summ))
            .map(|summ| summ.uniquename.clone())
            .collect()
    }

    fn detail_map_of_cv_annotations(&self, ont_annotation_map: &OntAnnotationMap)
                                    -> IdOntAnnotationDetailMap
    {
        let mut details_map = HashMap::new();

        for term_annotations in ont_annotation_map.values() {
            for term_annotation in term_annotations {
                for annotation_detail_id in &term_annotation.annotations {
                    let details =
                        &self.maps.annotation_details[annotation_detail_id];
                    details_map.insert(*annotation_detail_id, details.clone());
                }
            }
        }

        details_map
    }

    fn fill_term_map(&self, term_map: &TermShortOptionMap) -> TermShortOptionMap {
        let mut ret = term_map.clone();
        for termid in term_map.keys() {
            if let Some(term_details) = self.maps.terms.get(termid) {
                let term_short = TermShort::from_term_details(term_details);
                ret.insert(termid.clone(), Some(term_short));
            } else {
                eprint!("WARNING missing short info for: {}\n", termid);
                ret.insert(termid.clone(), None);
            }
        }
        ret
    }

    fn fill_gene_map(&self, gene_map: &GeneShortOptionMap) -> GeneShortOptionMap {
        let mut ret = gene_map.clone();
        for gene_uniquename in gene_map.keys() {
            let gene_details = &self.maps.genes[gene_uniquename];
            let gene_short = GeneShort::from_gene_details(gene_details);
            ret.insert(gene_uniquename.clone(), Some(gene_short));
        }
        ret
    }

    fn fill_reference_map(&self, reference_map: &ReferenceShortOptionMap) -> ReferenceShortOptionMap {
        let mut ret = reference_map.clone();
        for reference_uniquename in reference_map.keys() {
            let reference_details = &self.maps.references[reference_uniquename];
            let reference_short = ReferenceShort::from_reference_details(reference_details);
            ret.insert(reference_uniquename.clone(), Some(reference_short));
        }
        ret
    }

    // return a GeneDetails object with the term, genes and references maps filled in
    pub fn get_full_gene_details(&self, gene_uniquename: &str) -> Option<GeneDetails> {
        if let Some(gene_ref) = self.maps.genes.get(gene_uniquename) {
            let mut gene = gene_ref.clone();
            let details_map = self.detail_map_of_cv_annotations(&gene.cv_annotations);
            gene.annotation_details = details_map;
            gene.terms_by_termid = self.fill_term_map(&gene.terms_by_termid);
            gene.genes_by_uniquename = self.fill_gene_map(&gene.genes_by_uniquename);
            gene.references_by_uniquename =
                self.fill_reference_map(&gene.references_by_uniquename);
            Some(gene)
        } else {
            None
        }
    }

    pub fn get_genotype_details(&self, genotype_uniquename: &str) -> Option<GenotypeDetails> {
        if let Some(genotype_ref) = self.maps.genotypes.get(genotype_uniquename) {
            let mut genotype = genotype_ref.clone();
            let details_map = self.detail_map_of_cv_annotations(&genotype.cv_annotations);
            genotype.annotation_details = details_map;
            genotype.terms_by_termid = self.fill_term_map(&genotype.terms_by_termid);
            genotype.genes_by_uniquename = self.fill_gene_map(&genotype.genes_by_uniquename);
            genotype.references_by_uniquename =
                self.fill_reference_map(&genotype.references_by_uniquename);
            Some(genotype)
        } else {
            None
        }
    }

    fn term_details_helper(&self, term_ref: &TermDetails) -> Option<TermDetails> {
        let mut term = term_ref.clone();
        let details_map = self.detail_map_of_cv_annotations(&term.cv_annotations);
        term.annotation_details = details_map;
        term.terms_by_termid = self.fill_term_map(&term.terms_by_termid);
        term.genes_by_uniquename = self.fill_gene_map(&term.genes_by_uniquename);
        term.references_by_uniquename =
            self.fill_reference_map(&term.references_by_uniquename);
        Some(term)
    }

    pub fn get_term_details(&self, termid: &str) -> Option<TermDetails> {
        let termid =
            if let Some(real_termid) = self.secondary_identifiers_map.get(termid) {
                real_termid
            } else {
                termid
            };
        if let Some(term_ref) = self.maps.terms.get(termid) {
            self.term_details_helper(term_ref)
        } else {
            None
        }
    }

    pub fn get_reference_details(&self, reference_uniquename: &str) -> Option<ReferenceDetails> {
        if let Some(reference_ref) = self.maps.references.get(reference_uniquename) {
            let mut reference = reference_ref.clone();
            let details_map = self.detail_map_of_cv_annotations(&reference.cv_annotations);
            reference.annotation_details = details_map;
            reference.terms_by_termid = self.fill_term_map(&reference.terms_by_termid);
            reference.genes_by_uniquename = self.fill_gene_map(&reference.genes_by_uniquename);
            Some(reference)
        } else {
            None
        }
    }

    pub fn interactors_of_genes(&self, gene_uniquename: &GeneUniquename,
                                interaction_type: InteractionType)
                                -> Vec<GeneUniquename> {
        if let Some(interactors) = self.maps.interactors_of_genes.get(gene_uniquename) {
            interactors.iter()
                .filter(|interactor| {
                    interactor.interaction_type == interaction_type
                })
                .map(|interactor| interactor.interactor_uniquename.clone())
                .collect::<Vec<_>>()
        } else {
            vec![]
        }
    }

    pub fn get_chr_details(&self, chr_name: &str) -> Option<&ChromosomeDetails> {
        self.maps.chromosomes.get(chr_name)
    }
}
<commit_msg>Fix compile warning<commit_after>use std::process;
use std::fs::File;
use std::io::{Read, BufReader};

use serde_json;
use std::collections::{HashMap, HashSet};

use crate::data_types::{APIMaps, IdGeneSubsetMap, APIGeneSummary, APIAlleleDetails,
                GeneDetails, TermDetails, GenotypeDetails, ReferenceDetails,
                InteractionType, OntAnnotationMap, IdOntAnnotationDetailMap,
                TermShort, TermShortOptionMap, ChromosomeDetails,
                ReferenceShort, ReferenceShortOptionMap,
                GeneShort, GeneShortOptionMap, GeneQueryData};
use crate::web::config::{Config, TermAndName};
use crate::api::query::{SingleOrMultiAllele, QueryExpressionFilter};

use flate2::read::GzDecoder;

use crate::types::{TermId, GeneUniquename};

use pombase_rc_string::RcString;

pub struct APIData {
    maps: APIMaps,
    secondary_identifiers_map: HashMap<TermId, TermId>,
}

fn make_secondary_identifiers_map(terms: &HashMap<TermId, TermDetails>)
                                  -> HashMap<TermId, TermId>
{
    let mut ret_map = HashMap::new();

    for term_details in terms.values() {
        for secondary_identifier in &term_details.secondary_identifiers {
            ret_map.insert(secondary_identifier.clone(),
                           term_details.termid.clone());
        }
    }

    ret_map
}

impl APIData {
    pub fn new_from_file(config: &Config, search_maps_file_name: &str)
               -> APIData
    {
        let file = match File::open(search_maps_file_name) {
            Ok(file) => file,
            Err(err) => {
                eprint!("Failed to read {}: {}\n", search_maps_file_name, err);
                process::exit(1);
            }
        };
        let reader = BufReader::new(file);

        let mut decoder = GzDecoder::new(reader);
        let mut decoded_json = String::new();
        decoder.read_to_string(&mut decoded_json).unwrap();

        let maps: APIMaps =
            match serde_json::from_str(&decoded_json) {
                Ok(results) => results,
                Err(err) => {
                    eprint!("failed to parse {}: {}", search_maps_file_name, err);
                    process::exit(1);
                },
            };

        APIData::new(config, maps)
    }

    pub fn new(config: &Config, mut maps: APIMaps)
               ->APIData
    {
        let mut new_entries: IdGeneSubsetMap = HashMap::new();

        let prefixes_to_remove: Vec<String> =
            config.server.subsets.prefixes_to_remove
            .iter().map(|prefix| prefix.clone() + ":").collect();

        // strip prefixes and add to map
        for (subset_name, subset_details) in &maps.gene_subsets {
            for prefix in &prefixes_to_remove {
                if subset_name.starts_with(prefix) {
                    let new_subset_name = &subset_name[prefix.len()..];
                    new_entries.insert(RcString::from(new_subset_name), subset_details.clone());
                }
            }
        }

        maps.gene_subsets.extend(new_entries);

        let secondary_identifiers_map =
            make_secondary_identifiers_map(&maps.terms);

        APIData {
            secondary_identifiers_map,
            maps
        }
    }

    pub fn gene_uniquename_of_id(&self, id: &RcString) -> Option<GeneUniquename> {
        if self.maps.gene_summaries.contains_key(id) {
            Some(id.clone())
        } else {
            if let Some(gene_uniquename) =
                self.maps.gene_name_gene_map.get(id) {
                    Some(gene_uniquename.clone())
                } else {
                    let lower_case_id = id.to_lowercase();
                    for gene_uniquename in self.maps.gene_summaries.keys() {
                        if gene_uniquename.to_lowercase() == lower_case_id {
                            return Some(gene_uniquename.clone());
                        }
                    }
                    None
                }
        }
    }

    pub fn get_gene_summary(&self, gene_uniquename: &str) -> Option<&APIGeneSummary> {
        self.maps.gene_summaries.get(gene_uniquename)
    }

    pub fn get_gene_details(&self, gene_uniquename: &str) -> Option<&GeneDetails> {
        self.maps.genes.get(gene_uniquename)
    }

    pub fn get_gene_query_data(&self, gene_uniquename: &str) -> Option<&GeneQueryData> {
        self.maps.gene_query_data_map.get(gene_uniquename)
    }

    pub fn genes_of_termid(&self, term_id: &str) -> Vec<GeneUniquename> {
        match self.maps.termid_genes.get(term_id) {
            Some(gene_uniquenames) => {
                gene_uniquenames.iter().cloned().collect::<Vec<_>>()
            },
            None => vec![],
        }
    }

    pub fn genes_of_subset(&self, search_name: &str) -> Vec<GeneUniquename> {
        if search_name.starts_with('!') || search_name.ends_with('*') {
            let mut trimmed_search_name = search_name.to_owned();
            let invert_search = search_name.starts_with('!');
            let wildcard = search_name.ends_with('*');
            if invert_search {
                trimmed_search_name.remove(0);
            }
            if wildcard {
                trimmed_search_name.pop();
            }
            let mut genes = HashSet::new();
            for (subset_name, subset_details) in &self.maps.gene_subsets {
                let name_matches =
                    wildcard && subset_name.starts_with(&trimmed_search_name) ||
                    trimmed_search_name.eq(subset_name);

                if !invert_search && name_matches || invert_search && !name_matches {
                    genes.extend(subset_details.elements.iter().cloned());
                }
            }
            genes.iter().cloned().collect::<Vec<_>>()
        } else {
            match self.maps.gene_subsets.get(search_name) {
                Some(subset_details) => {
                    subset_details.elements.iter().cloned().collect::<Vec<_>>()
                },
                None => vec![],
            }
        }
    }

    pub fn genes_of_genotypes(&self, term_id: &str,
                              single_or_multi_allele: &SingleOrMultiAllele,
                              expression_filter: &Option<QueryExpressionFilter>,
                              conditions_filter: &HashSet<TermAndName>,
                              excluded_conditions_filter: &HashSet<TermAndName>)
                              -> Vec<GeneUniquename>
    {
        if let Some(annotations) = self.maps.termid_genotype_annotation.get(term_id) {
            let mut genes: HashSet<GeneUniquename> = HashSet::new();
            for annotation in annotations {

                let mut add_single = false;
                let mut add_multi = false;

                match *single_or_multi_allele {
                    SingleOrMultiAllele::Single => add_single = true,
                    SingleOrMultiAllele::Multi => add_multi = true,
                    SingleOrMultiAllele::Both => {
                        add_single = true;
                        add_multi = true;
                    },
                }

                let expression_matches =
                    |allele_details: &APIAlleleDetails| {
                        let expression = &allele_details.expression;
                        let allele_type = &allele_details.allele_type;
                        if let Some(ref expression_filter) = *expression_filter {
                            if *expression_filter == QueryExpressionFilter::Any {
                                return true;
                            }
                            if *expression_filter == QueryExpressionFilter::Null {
                                if allele_type == "deletion" {
                                    return true;
                                }
                                if let Some(ref expression) = *expression {
                                    expression == "Null"
                                } else {
                                    false
                                }
                            } else {
                                if *expression_filter == QueryExpressionFilter::WtOverexpressed {
                                    if let Some(ref expression) = *expression {
                                        expression == "Overexpression" &&
                                            allele_type == "wild_type"
                                    } else {
                                        false
                                    }
                                } else {
                                    false
                                }
                            }
                        } else {
                            true
                        }
                    };

                let condition_matches =
                    |conditions: &HashSet<TermAndName>, test_conditions_filter: &HashSet<TermAndName>| {
                        if test_conditions_filter.len() == 0 {
                            // don't filter in this case
                            return true;
                        }

                        for filter_condition in test_conditions_filter {
                            if conditions.iter().find(
                                |cond| {
                                    filter_condition.termid == cond.termid
                                })
                                .is_some() {
                                return true
                            }
                        }

                        false
                    };

                if annotation.is_multi && add_multi ||
                    !annotation.is_multi && add_single &&
                    expression_matches(&annotation.alleles[0]) &&
                    condition_matches(&annotation.conditions, conditions_filter) &&
                    (excluded_conditions_filter.len() == 0 ||
                     !condition_matches(&annotation.conditions, excluded_conditions_filter)) {
                        for allele_details in &annotation.alleles {
                            genes.insert(allele_details.gene.clone());
                        }
                }
            }
            genes.into_iter().collect::<Vec<_>>()
        } else {
            vec![]
        }
    }

    pub fn filter_genes(&self, p: & dyn Fn(&APIGeneSummary) -> bool)
                        -> Vec<GeneUniquename>
    {
        self.maps.gene_summaries.values()
            .filter(|summ| p(summ))
            .map(|summ| summ.uniquename.clone())
            .collect()
    }

    fn detail_map_of_cv_annotations(&self, ont_annotation_map: &OntAnnotationMap)
                                    -> IdOntAnnotationDetailMap
    {
        let mut details_map = HashMap::new();

        for term_annotations in ont_annotation_map.values() {
            for term_annotation in term_annotations {
                for annotation_detail_id in &term_annotation.annotations {
                    let details =
                        &self.maps.annotation_details[annotation_detail_id];
                    details_map.insert(*annotation_detail_id, details.clone());
                }
            }
        }

        details_map
    }

    fn fill_term_map(&self, term_map: &TermShortOptionMap) -> TermShortOptionMap {
        let mut ret = term_map.clone();
        for termid in term_map.keys() {
            if let Some(term_details) = self.maps.terms.get(termid) {
                let term_short = TermShort::from_term_details(term_details);
                ret.insert(termid.clone(), Some(term_short));
            } else {
                eprint!("WARNING missing short info for: {}\n", termid);
                ret.insert(termid.clone(), None);
            }
        }
        ret
    }

    fn fill_gene_map(&self, gene_map: &GeneShortOptionMap) -> GeneShortOptionMap {
        let mut ret = gene_map.clone();
        for gene_uniquename in gene_map.keys() {
            let gene_details = &self.maps.genes[gene_uniquename];
            let gene_short = GeneShort::from_gene_details(gene_details);
            ret.insert(gene_uniquename.clone(), Some(gene_short));
        }
        ret
    }

    fn fill_reference_map(&self, reference_map: &ReferenceShortOptionMap) -> ReferenceShortOptionMap {
        let mut ret = reference_map.clone();
        for reference_uniquename in reference_map.keys() {
            let reference_details = &self.maps.references[reference_uniquename];
            let reference_short = ReferenceShort::from_reference_details(reference_details);
            ret.insert(reference_uniquename.clone(), Some(reference_short));
        }
        ret
    }

    // return a GeneDetails object with the term, genes and references maps filled in
    pub fn get_full_gene_details(&self, gene_uniquename: &str) -> Option<GeneDetails> {
        if let Some(gene_ref) = self.maps.genes.get(gene_uniquename) {
            let mut gene = gene_ref.clone();
            let details_map = self.detail_map_of_cv_annotations(&gene.cv_annotations);
            gene.annotation_details = details_map;
            gene.terms_by_termid = self.fill_term_map(&gene.terms_by_termid);
            gene.genes_by_uniquename = self.fill_gene_map(&gene.genes_by_uniquename);
            gene.references_by_uniquename =
                self.fill_reference_map(&gene.references_by_uniquename);
            Some(gene)
        } else {
            None
        }
    }

    pub fn get_genotype_details(&self, genotype_uniquename: &str) -> Option<GenotypeDetails> {
        if let Some(genotype_ref) = self.maps.genotypes.get(genotype_uniquename) {
            let mut genotype = genotype_ref.clone();
            let details_map = self.detail_map_of_cv_annotations(&genotype.cv_annotations);
            genotype.annotation_details = details_map;
            genotype.terms_by_termid = self.fill_term_map(&genotype.terms_by_termid);
            genotype.genes_by_uniquename = self.fill_gene_map(&genotype.genes_by_uniquename);
            genotype.references_by_uniquename =
                self.fill_reference_map(&genotype.references_by_uniquename);
            Some(genotype)
        } else {
            None
        }
    }

    fn term_details_helper(&self, term_ref: &TermDetails) -> Option<TermDetails> {
        let mut term = term_ref.clone();
        let details_map = self.detail_map_of_cv_annotations(&term.cv_annotations);
        term.annotation_details = details_map;
        term.terms_by_termid = self.fill_term_map(&term.terms_by_termid);
        term.genes_by_uniquename = self.fill_gene_map(&term.genes_by_uniquename);
        term.references_by_uniquename =
            self.fill_reference_map(&term.references_by_uniquename);
        Some(term)
    }

    pub fn get_term_details(&self, termid: &str) -> Option<TermDetails> {
        let termid =
            if let Some(real_termid) = self.secondary_identifiers_map.get(termid) {
                real_termid
            } else {
                termid
            };
        if let Some(term_ref) = self.maps.terms.get(termid) {
            self.term_details_helper(term_ref)
        } else {
            None
        }
    }

    pub fn get_reference_details(&self, reference_uniquename: &str) -> Option<ReferenceDetails> {
        if let Some(reference_ref) = self.maps.references.get(reference_uniquename) {
            let mut reference = reference_ref.clone();
            let details_map = self.detail_map_of_cv_annotations(&reference.cv_annotations);
            reference.annotation_details = details_map;
            reference.terms_by_termid = self.fill_term_map(&reference.terms_by_termid);
            reference.genes_by_uniquename = self.fill_gene_map(&reference.genes_by_uniquename);
            Some(reference)
        } else {
            None
        }
    }

    pub fn interactors_of_genes(&self, gene_uniquename: &GeneUniquename,
                                interaction_type: InteractionType)
                                -> Vec<GeneUniquename> {
        if let Some(interactors) = self.maps.interactors_of_genes.get(gene_uniquename) {
            interactors.iter()
                .filter(|interactor| {
                    interactor.interaction_type == interaction_type
                })
                .map(|interactor| interactor.interactor_uniquename.clone())
                .collect::<Vec<_>>()
        } else {
            vec![]
        }
    }

    pub fn get_chr_details(&self, chr_name: &str) -> Option<&ChromosomeDetails> {
        self.maps.chromosomes.get(chr_name)
    }
}
<|endoftext|>


<commit_before>
// Provides the ```pnacl_test! {}``` macro.
// Usage:
//
// try one:
// ```rust
// pnacl_test! {
//    fn test_name() {
//        extern crate ppapi;
//        use std::collections::HashMap;
//        #[no_mangle]
//        pub extern fn ppapi_instance_created(_instance: ppapi::Instance,
//                                             _args: HashMap<String, String>) {
//            // Test logic here.
//        }
//    }
// }
// ```
// try two:
// ```rust
// #[ppapi_test]
// fn test_name(instance: ppapi::Instance) {
// }
// ```

#![crate_name = "ppapi-tester"]
#![crate_type = "dylib"]
#![feature(plugin_registrar, quote)]

#![allow(unstable)]

extern crate rustc;
extern crate syntax;

use syntax::ext::base::{SyntaxExtension, ExtCtxt};
use syntax::ext::build::AstBuilder;
use syntax::ext::quote::rt::{ToSource, ToTokens};
use syntax::parse::token::{intern, intern_and_get_ident};
use syntax::{ast, codemap};
use syntax::codemap::Span;
use syntax::ptr::P;
use rustc::plugin::Registry;

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    let ppapi_test_expander = SyntaxExtension::Modifier(Box::new(expand_test));
    let ppapi_test = intern("ppapi_test");
    reg.register_syntax_extension(ppapi_test, ppapi_test_expander);
}

const HTML: &'static str = include_str!("index.html");
const MANIFEST: &'static str = include_str!("manifest.nmf");

fn expand_test(ecx: &mut ExtCtxt,
               sp: Span,
               _meta_item: &ast::MetaItem,
               item: P<ast::Item>) -> P<ast::Item> {
    // we replace the input function with a function that instead compiles the
    // pretty printed original to a pexe and invokes chrome to simulate being
    // embedding in a website.

    println!("============");
    println!("{:?}", item);

    #[derive(PartialEq)]
    enum HasTestSignature {
        Yes,
        No,
        NotEvenAFunction,
    }

    fn has_correct_signature(i: &ast::Item) -> HasTestSignature {
        match &i.node {
          &ast::ItemFn(ref decl, _, _, ref generics, _) => {
            let no_output = match decl.output {
                ast::DefaultReturn(..) => true,
                _ => false
            };
            if decl.inputs.len() == 2
                   && no_output
                   && !generics.is_parameterized() {
                HasTestSignature::Yes
            } else {
                HasTestSignature::No
            }
          }
          _ => HasTestSignature::NotEvenAFunction,
        }
    }

    // first check that the annotated item is, in fact, a function:
    match has_correct_signature(&*item) {
        HasTestSignature::Yes => { println!("sig is fine"); },
        HasTestSignature::No => {
            ecx.span_err(sp, "function must be typed: fn(instance: \
                         ppapi::Instance, args: HashMap<String, String>) -> ()");
            return item;
        },
        HasTestSignature::NotEvenAFunction => {
            ecx.span_err(sp, "must be a function: fn(instance: ppapi::Instance, \
                         args: HashMap<String, String>) -> ()");
            return item;
        },
    }

    let test_name = item.ident;
    let wrapper =
        quote_item!(
            ecx,
            #![no_main]
            extern crate ppapi;

            use std::collections::HashMap;

            struct ResultMessager(ppapi::Messaging);
            impl Drop for ResultMessager {
                fn drop(&mut self) {
                    use std::rt::unwind::panicking;
                    let &mut ResultMessager(msg) = self;

                    if panicking() {
                        msg.post_message("failure");
                    } else {
                        msg.post_message("success");
                    }
                }
            }

            $item

            #[no_mangle] pub extern fn ppapi_instance_created(instance: ppapi::Instance,
                                                              args: HashMap<String, String>) {
                use ppapi::MessageLoop;
                assert!(MessageLoop::post_to_self(move |: _| {
                    let result = ResultMessager(instance.messaging());
                    $test_name(instance.clone(), args.clone());
                }).is_ok());
                assert!(MessageLoop::current().unwrap().shutdown().is_ok());
            }
            #[no_mangle] pub extern fn ppapi_instance_destroyed() { }
            );
    let wrapper = wrapper.expect("the wrapper failed to quote");
    let wrapper_ident = intern_and_get_ident(wrapper.to_source().as_slice());
    let wrapper_str = ecx.expr_str(sp, wrapper_ident);

    let cwd = ::std::os::getcwd().unwrap();
    let deps_str = cwd.join_many(["target", "le32-unknown-nacl", "deps"].as_slice())
        .display().to_string();
    let deps_ident = intern_and_get_ident(deps_str.as_slice());
    let deps_dir = ecx.expr_str(codemap::DUMMY_SP, deps_ident);

    let timeout = None::<u64>;

    let host_test =
        quote_item!(
            ecx,
            #[test] fn $test_name() {
                use std::io::TempDir;
                use std::io::fs::File;
                use std::io::{Command, LineBufferedWriter};
                use std::io::process::{ProcessExit, ProcessOutput};
                use std::io::stdio::{stdout, stderr, StdWriter};

                // the source to the real test:
                const REAL_TEST: &'static str = $wrapper_str;

                let tmp = TempDir::new("ppapi-test")
                    .ok().expect("need temp directory for test artifacts");

                let url = format!("file://{}",
                                  tmp.path().join("index.html").display());
                let chrome_args = ["--allow-file-access-from-files".to_string(),
                                   "--bwsi".to_string(),
                                   "--no-sandbox".to_string(),
                                   "--silent-launch".to_string(),
                                   "--noerrdialogs".to_string(),
                                   "--no-first-run".to_string(),
                                   "--no-default-browser-check".to_string(),
                                   url];

                let stdout_path = tmp.path().join("stdout");
                let stdout_name = stdout_path
                    .display().to_string();
                let stderr_path = tmp.path().join("stderr");
                let stderr_name = stderr_path
                    .display().to_string();
                let chrome_env = [("NACL_EXE_STDOUT".to_string(), stdout_name),
                                  ("NACL_EXE_STDERR".to_string(), stderr_name)];

                let rustc_args = ["-".to_string(),
                                  "-o".to_string(),
                                  tmp.path().join("test-main.pexe").display().to_string(),
                                  "--crate-type".to_string(), "bin".to_string(),
                                  "--target".to_string(),
                                  "le32-unknown-nacl".to_string(),
                                  "-L".to_string(),
                                  ($deps_dir).to_string()];
                let mut rustc = Command::new("rustc");
                rustc.args(rustc_args);
                println!("∨∨∨∨∨∨∨∨∨∨∨    rustc    ∨∨∨∨∨∨∨∨∨∨∨");
                println!("spawning `{:?}`", rustc);
                let mut rustc = rustc.spawn().unwrap();
                rustc.stdin
                    .as_mut()
                    .unwrap()
                    .write_str(REAL_TEST)
                    .unwrap();

                {
                    let path = tmp.path()
                        .join("index.html");
                    let file = File::create(&path).unwrap();
                    file.write_str($HTML).unwrap();
                }
                {
                    let path = tmp.path()
                        .join("manifest.nmf");
                    let file = File::create(&path).unwrap();
                    file.write_str($MANIFEST).unwrap();
                }

                match rustc.wait_with_output() {
                    Ok(ProcessOutput {
                        status: exit_status,
                        output: out, error: err,
                    }) => {
                        stdout().write(out.as_slice());
                        stderr().write(err.as_slice());
                        assert!(exit_status.success());
                    }
                    err => { err.unwrap(); },
                }
                println!("∧∧∧∧∧∧∧∧∧∧∧    rustc    ∧∧∧∧∧∧∧∧∧∧∧");

                let mut chrome = Command::new("google-chrome");
                chrome.args(chrome_args);
                for (k, v) in chrome_env.iter() {
                    chrome.env(k, v);
                }
                println!("∨∨∨∨∨∨∨∨∨∨∨   chrome    ∨∨∨∨∨∨∨∨∨∨∨");
                println!("spawning `{:?}`:", chrome);

                let mut chrome = chrome.spawn().unwrap();
                chrome.set_timeout($timeout);

                fn split_and_print(where_: LineBufferedWriter<StdWriter>,
                                   kind: &'static str, out: &[u8]) {
                    for line in out.split(move |: v| v == '\n' ) {
                        where_.write_str(kind).unwrap();
                        where_.write(line).unwrap();
                        where_.write_u8('\n').unwrap();
                    }
                }

                fn dump_output(cmd: &mut Process) {
                    let output = cmd.stdout
                        .as_mut().unwrap()
                        .read_to_end().unwrap();
                    let error = cmd.stderr
                        .as_mut().unwrap()
                        .read_to_end().unwrap();
                    split_and_print(stdout(), "stdout: ", output.as_slice());
                    split_and_print(stderr(), "stderr: ", error.as_slice());
                }

                match chrome.wait() {
                    Ok(status) => {
                        dump_output(&mut chrome);
                        assert!(status.success());
                    },
                    Err(IoError { kind: IoErrorKind::TimedOut, .. }) => {
                        println!("timeout! sending sigterm...");
                        chrome.set_timeout(Some(6000));
                        chrome.signal_exit().unwrap();
                        let res = chrome.wait();
                        let res = match res {
                            Err(IoError { kind: IoErrorKind::TimedOut, .. }) => {
                                println!("1s later: chrome still hasn't quit; killing.");
                                chrome.signal_kill().unwrap();
                                chrome.wait()
                            },
                            res => res,
                        }
                        dump_output(&mut chrome);
                        assert_eq!(res, Ok(ProcessExit::ExitStatus(0))); // always false.
                        unreachable!()
                    },
                    res => {
                        dump_output(&mut chrome);
                        res.unwrap();
                        unreachable!()
                    }
                }
                println!("∧∧∧∧∧∧∧∧∧∧∧   chrome    ∧∧∧∧∧∧∧∧∧∧∧");

                println!("∨∨∨∨∨∨∨∨∨∨∨ test output ∨∨∨∨∨∨∨∨∨∨∨");
                fn read_log(p: &Path) -> Vec<u8> {
                    let f = File::open(p).unwrap();
                    f.read_to_end().unwrap()
                }
                let test_stdout = read_log(&stdout_path);
                let test_stderr = read_log(&stderr_path);
                split_and_print(stdout(), "test stdout: ", test_stdout.as_slice());
                split_and_print(stderr(), "test stderr: ", test_stderr.as_slice());
                println!("∧∧∧∧∧∧∧∧∧∧∧ test output ∧∧∧∧∧∧∧∧∧∧∧");
            });
    host_test.unwrap()
}
<commit_msg>Use the test name when creating a temp dir.<commit_after>
// Provides the ```pnacl_test! {}``` macro.
// Usage:
//
// try one:
// ```rust
// pnacl_test! {
//    fn test_name() {
//        extern crate ppapi;
//        use std::collections::HashMap;
//        #[no_mangle]
//        pub extern fn ppapi_instance_created(_instance: ppapi::Instance,
//                                             _args: HashMap<String, String>) {
//            // Test logic here.
//        }
//    }
// }
// ```
// try two:
// ```rust
// #[ppapi_test]
// fn test_name(instance: ppapi::Instance) {
// }
// ```

#![crate_name = "ppapi-tester"]
#![crate_type = "dylib"]
#![feature(plugin_registrar, quote)]

#![allow(unstable)]

extern crate rustc;
extern crate syntax;

use syntax::ext::base::{SyntaxExtension, ExtCtxt};
use syntax::ext::build::AstBuilder;
use syntax::ext::quote::rt::{ToSource, ToTokens};
use syntax::parse::token::{intern, intern_and_get_ident};
use syntax::{ast, codemap};
use syntax::codemap::Span;
use syntax::ptr::P;
use rustc::plugin::Registry;

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    let ppapi_test_expander = SyntaxExtension::Modifier(Box::new(expand_test));
    let ppapi_test = intern("ppapi_test");
    reg.register_syntax_extension(ppapi_test, ppapi_test_expander);
}

const HTML: &'static str = include_str!("index.html");
const MANIFEST: &'static str = include_str!("manifest.nmf");

fn expand_test(ecx: &mut ExtCtxt,
               sp: Span,
               _meta_item: &ast::MetaItem,
               item: P<ast::Item>) -> P<ast::Item> {
    // we replace the input function with a function that instead compiles the
    // pretty printed original to a pexe and invokes chrome to simulate being
    // embedding in a website.

    println!("============");
    println!("{:?}", item);

    #[derive(PartialEq)]
    enum HasTestSignature {
        Yes,
        No,
        NotEvenAFunction,
    }

    fn has_correct_signature(i: &ast::Item) -> HasTestSignature {
        match &i.node {
          &ast::ItemFn(ref decl, _, _, ref generics, _) => {
            let no_output = match decl.output {
                ast::DefaultReturn(..) => true,
                _ => false
            };
            if decl.inputs.len() == 2
                   && no_output
                   && !generics.is_parameterized() {
                HasTestSignature::Yes
            } else {
                HasTestSignature::No
            }
          }
          _ => HasTestSignature::NotEvenAFunction,
        }
    }

    // first check that the annotated item is, in fact, a function:
    match has_correct_signature(&*item) {
        HasTestSignature::Yes => { println!("sig is fine"); },
        HasTestSignature::No => {
            ecx.span_err(sp, "function must be typed: fn(instance: \
                         ppapi::Instance, args: HashMap<String, String>) -> ()");
            return item;
        },
        HasTestSignature::NotEvenAFunction => {
            ecx.span_err(sp, "must be a function: fn(instance: ppapi::Instance, \
                         args: HashMap<String, String>) -> ()");
            return item;
        },
    }

    let test_name = item.ident;
    let wrapper =
        quote_item!(
            ecx,
            #![no_main]
            extern crate ppapi;

            use std::collections::HashMap;

            struct ResultMessager(ppapi::Messaging);
            impl Drop for ResultMessager {
                fn drop(&mut self) {
                    use std::rt::unwind::panicking;
                    let &mut ResultMessager(msg) = self;

                    if panicking() {
                        msg.post_message("failure");
                    } else {
                        msg.post_message("success");
                    }
                }
            }

            $item

            #[no_mangle] pub extern fn ppapi_instance_created(instance: ppapi::Instance,
                                                              args: HashMap<String, String>) {
                use ppapi::MessageLoop;
                assert!(MessageLoop::post_to_self(move |: _| {
                    let result = ResultMessager(instance.messaging());
                    $test_name(instance.clone(), args.clone());
                }).is_ok());
                assert!(MessageLoop::current().unwrap().shutdown().is_ok());
            }
            #[no_mangle] pub extern fn ppapi_instance_destroyed() { }
            );
    let wrapper = wrapper.expect("the wrapper failed to quote");
    let wrapper_ident = intern_and_get_ident(wrapper.to_source().as_slice());
    let wrapper_str = ecx.expr_str(sp, wrapper_ident);

    let cwd = ::std::os::getcwd().unwrap();
    let deps_str = cwd.join_many(["target", "le32-unknown-nacl", "deps"].as_slice())
        .display().to_string();
    let deps_ident = intern_and_get_ident(deps_str.as_slice());
    let deps_dir = ecx.expr_str(codemap::DUMMY_SP, deps_ident);

    let timeout = None::<u64>;

    let host_test =
        quote_item!(
            ecx,
            #[test] fn $test_name() {
                use std::io::TempDir;
                use std::io::fs::File;
                use std::io::{Command, LineBufferedWriter};
                use std::io::process::{ProcessExit, ProcessOutput};
                use std::io::stdio::{stdout, stderr, StdWriter};

                // the source to the real test:
                const REAL_TEST: &'static str = $wrapper_str;

                let tmp = TempDir::new(stringify!($test_name))
                    .ok().expect("need temp directory for test artifacts");

                let url = format!("file://{}",
                                  tmp.path().join("index.html").display());
                let chrome_args = ["--allow-file-access-from-files".to_string(),
                                   "--bwsi".to_string(),
                                   "--no-sandbox".to_string(),
                                   "--silent-launch".to_string(),
                                   "--noerrdialogs".to_string(),
                                   "--no-first-run".to_string(),
                                   "--no-default-browser-check".to_string(),
                                   url];

                let stdout_path = tmp.path().join("stdout");
                let stdout_name = stdout_path
                    .display().to_string();
                let stderr_path = tmp.path().join("stderr");
                let stderr_name = stderr_path
                    .display().to_string();
                let chrome_env = [("NACL_EXE_STDOUT".to_string(), stdout_name),
                                  ("NACL_EXE_STDERR".to_string(), stderr_name)];

                let rustc_args = ["-".to_string(),
                                  "-o".to_string(),
                                  tmp.path().join("test-main.pexe").display().to_string(),
                                  "--crate-type".to_string(), "bin".to_string(),
                                  "--target".to_string(),
                                  "le32-unknown-nacl".to_string(),
                                  "-L".to_string(),
                                  ($deps_dir).to_string()];
                let mut rustc = Command::new("rustc");
                rustc.args(rustc_args);
                println!("∨∨∨∨∨∨∨∨∨∨∨    rustc    ∨∨∨∨∨∨∨∨∨∨∨");
                println!("spawning `{:?}`", rustc);
                let mut rustc = rustc.spawn().unwrap();
                rustc.stdin
                    .as_mut()
                    .unwrap()
                    .write_str(REAL_TEST)
                    .unwrap();

                {
                    let path = tmp.path()
                        .join("index.html");
                    let file = File::create(&path).unwrap();
                    file.write_str($HTML).unwrap();
                }
                {
                    let path = tmp.path()
                        .join("manifest.nmf");
                    let file = File::create(&path).unwrap();
                    file.write_str($MANIFEST).unwrap();
                }

                match rustc.wait_with_output() {
                    Ok(ProcessOutput {
                        status: exit_status,
                        output: out, error: err,
                    }) => {
                        stdout().write(out.as_slice());
                        stderr().write(err.as_slice());
                        assert!(exit_status.success());
                    }
                    err => { err.unwrap(); },
                }
                println!("∧∧∧∧∧∧∧∧∧∧∧    rustc    ∧∧∧∧∧∧∧∧∧∧∧");

                let mut chrome = Command::new("google-chrome");
                chrome.args(chrome_args);
                for (k, v) in chrome_env.iter() {
                    chrome.env(k, v);
                }
                println!("∨∨∨∨∨∨∨∨∨∨∨   chrome    ∨∨∨∨∨∨∨∨∨∨∨");
                println!("spawning `{:?}`:", chrome);

                let mut chrome = chrome.spawn().unwrap();
                chrome.set_timeout($timeout);

                fn split_and_print(where_: LineBufferedWriter<StdWriter>,
                                   kind: &'static str, out: &[u8]) {
                    for line in out.split(move |: v| v == '\n' ) {
                        where_.write_str(kind).unwrap();
                        where_.write(line).unwrap();
                        where_.write_u8('\n').unwrap();
                    }
                }

                fn dump_output(cmd: &mut Process) {
                    let output = cmd.stdout
                        .as_mut().unwrap()
                        .read_to_end().unwrap();
                    let error = cmd.stderr
                        .as_mut().unwrap()
                        .read_to_end().unwrap();
                    split_and_print(stdout(), "stdout: ", output.as_slice());
                    split_and_print(stderr(), "stderr: ", error.as_slice());
                }

                match chrome.wait() {
                    Ok(status) => {
                        dump_output(&mut chrome);
                        assert!(status.success());
                    },
                    Err(IoError { kind: IoErrorKind::TimedOut, .. }) => {
                        println!("timeout! sending sigterm...");
                        chrome.set_timeout(Some(6000));
                        chrome.signal_exit().unwrap();
                        let res = chrome.wait();
                        let res = match res {
                            Err(IoError { kind: IoErrorKind::TimedOut, .. }) => {
                                println!("1s later: chrome still hasn't quit; killing.");
                                chrome.signal_kill().unwrap();
                                chrome.wait()
                            },
                            res => res,
                        }
                        dump_output(&mut chrome);
                        assert_eq!(res, Ok(ProcessExit::ExitStatus(0))); // always false.
                        unreachable!()
                    },
                    res => {
                        dump_output(&mut chrome);
                        res.unwrap();
                        unreachable!()
                    }
                }
                println!("∧∧∧∧∧∧∧∧∧∧∧   chrome    ∧∧∧∧∧∧∧∧∧∧∧");

                println!("∨∨∨∨∨∨∨∨∨∨∨ test output ∨∨∨∨∨∨∨∨∨∨∨");
                fn read_log(p: &Path) -> Vec<u8> {
                    let f = File::open(p).unwrap();
                    f.read_to_end().unwrap()
                }
                let test_stdout = read_log(&stdout_path);
                let test_stderr = read_log(&stderr_path);
                split_and_print(stdout(), "test stdout: ", test_stdout.as_slice());
                split_and_print(stderr(), "test stderr: ", test_stderr.as_slice());
                println!("∧∧∧∧∧∧∧∧∧∧∧ test output ∧∧∧∧∧∧∧∧∧∧∧");
            });
    host_test.unwrap()
}
<|endoftext|>


<commit_before>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::result::Result;

use RequestError;

/// Wrapper over an HTTP Header type.
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum Header {
    /// Header `Content-Length`.
    ContentLength,
    /// Header `Content-Type`.
    ContentType,
    /// Header `Expect`.
    Expect,
    /// Header `Transfer-Encoding`.
    TransferEncoding,
    /// Header `Server`.
    Server,
}

impl Header {
    /// Returns a byte slice representation of the object.
    pub fn raw(&self) -> &'static [u8] {
        match self {
            Self::ContentLength => b"Content-Length",
            Self::ContentType => b"Content-Type",
            Self::Expect => b"Expect",
            Self::TransferEncoding => b"Transfer-Encoding",
            Self::Server => b"Server",
        }
    }

    /// Parses a byte slice into a Header structure. Header must be ASCII, so also
    /// UTF-8 valid.
    ///
    /// # Errors
    /// `InvalidRequest` is returned if slice contains invalid utf8 characters.
    /// `InvalidHeader` is returned if unsupported header found.
    fn try_from(string: &[u8]) -> Result<Self, RequestError> {
        if let Ok(mut utf8_string) = String::from_utf8(string.to_vec()) {
            utf8_string.make_ascii_lowercase();
            match utf8_string.trim() {
                "content-length" => Ok(Self::ContentLength),
                "content-type" => Ok(Self::ContentType),
                "expect" => Ok(Self::Expect),
                "transfer-encoding" => Ok(Self::TransferEncoding),
                "server" => Ok(Self::Server),
                _ => Err(RequestError::InvalidHeader),
            }
        } else {
            Err(RequestError::InvalidRequest)
        }
    }
}

/// Wrapper over the list of headers associated with a Request that we need
/// in order to parse the request correctly and be able to respond to it.
///
/// The only `Content-Type`s supported are `text/plain` and `application/json`, which are both
/// in plain text actually and don't influence our parsing process.
///
/// All the other possible header fields are not necessary in order to serve this connection
/// and, thus, are not of interest to us. However, we still look for header fields that might
/// invalidate our request as we don't support the full set of HTTP/1.1 specification.
/// Such header entries are "Transfer-Encoding: identity; q=0", which means a compression
/// algorithm is applied to the body of the request, or "Expect: 103-checkpoint".
#[derive(Debug)]
pub struct Headers {
    /// The `Content-Length` header field tells us how many bytes we need to receive
    /// from the source after the headers.
    content_length: i32,
    /// The `Expect` header field is set when the headers contain the entry "Expect: 100-continue".
    /// This means that, per HTTP/1.1 specifications, we must send a response with the status code
    /// 100 after we have received the headers in order to receive the body of the request. This
    /// field should be known immediately after parsing the headers.
    expect: bool,
    /// `Chunked` is a possible value of the `Transfer-Encoding` header field and every HTTP/1.1
    /// server must support it. It is useful only when receiving the body of the request and should
    /// be known immediately after parsing the headers.
    chunked: bool,
}

impl Default for Headers {
    /// By default Requests are created with no headers.
    fn default() -> Self {
        Self {
            content_length: Default::default(),
            expect: Default::default(),
            chunked: Default::default(),
        }
    }
}

impl Headers {
    /// Expects one header line and parses it, updating the header structure or returning an
    /// error if the header is invalid.
    ///
    /// # Errors
    /// `UnsupportedHeader` is returned when the parsed header line is not of interest
    /// to us or when it is unrecognizable.
    /// `InvalidHeader` is returned when the parsed header is formatted incorrectly or suggests
    /// that the client is using HTTP features that we do not support in this implementation,
    /// which invalidates the request.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::Headers;
    ///
    /// let mut request_header = Headers::default();
    /// assert!(request_header.parse_header_line(b"Content-Length: 24").is_ok());
    /// assert!(request_header.parse_header_line(b"Content-Length: 24: 2").is_err());
    /// ```
    pub fn parse_header_line(&mut self, header_line: &[u8]) -> Result<(), RequestError> {
        // Headers must be ASCII, so also UTF-8 valid.
        match std::str::from_utf8(header_line) {
            Ok(headers_str) => {
                let entry = headers_str.split(": ").collect::<Vec<&str>>();
                if entry.len() != 2 {
                    return Err(RequestError::InvalidHeader);
                }
                if let Ok(head) = Header::try_from(entry[0].as_bytes()) {
                    match head {
                        Header::ContentLength => match entry[1].trim().parse::<i32>() {
                            Ok(content_length) => {
                                self.content_length = content_length;
                                Ok(())
                            }
                            Err(_) => Err(RequestError::InvalidHeader),
                        },
                        Header::ContentType => {
                            match MediaType::try_from(entry[1].trim().as_bytes()) {
                                Ok(_) => Ok(()),
                                Err(_) => Err(RequestError::UnsupportedHeader),
                            }
                        }
                        Header::TransferEncoding => match entry[1].trim() {
                            "chunked" => {
                                self.chunked = true;
                                Ok(())
                            }
                            "identity; q=0" => Err(RequestError::InvalidHeader),
                            _ => Err(RequestError::UnsupportedHeader),
                        },
                        Header::Expect => match entry[1].trim() {
                            "100-continue" => {
                                self.expect = true;
                                Ok(())
                            }
                            _ => Err(RequestError::InvalidHeader),
                        },
                        Header::Server => Ok(()),
                    }
                } else {
                    Err(RequestError::UnsupportedHeader)
                }
            }
            _ => Err(RequestError::InvalidHeader),
        }
    }

    /// Returns the content length of the body.
    pub fn content_length(&self) -> i32 {
        self.content_length
    }

    /// Returns `true` if the transfer encoding is chunked.
    #[allow(unused)]
    pub fn chunked(&self) -> bool {
        self.chunked
    }

    /// Returns `true` if the client is expecting the code 100.
    #[allow(unused)]
    pub fn expect(&self) -> bool {
        self.expect
    }

    /// Parses a byte slice into a Headers structure for a HTTP request.
    ///
    /// The byte slice is expected to have the following format: </br>
    ///     * Request Header Lines "<header_line> CRLF"- Optional </br>
    /// There can be any number of request headers, including none, followed by
    /// an extra sequence of Carriage Return and Line Feed.
    /// All header fields are parsed. However, only the ones present in the
    /// [`Headers`](struct.Headers.html) struct are relevant to us and stored
    /// for future use.
    ///
    /// # Errors
    /// The function returns `InvalidHeader` when parsing the byte stream fails.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::Headers;
    ///
    /// let request_headers = Headers::try_from(b"Content-Length: 55\r\n\r\n");
    /// ```
    pub fn try_from(bytes: &[u8]) -> Result<Headers, RequestError> {
        // Headers must be ASCII, so also UTF-8 valid.
        if let Ok(text) = std::str::from_utf8(bytes) {
            let mut headers = Self::default();

            let header_lines = text.split("\r\n");
            for header_line in header_lines {
                if header_line.is_empty() {
                    break;
                }
                match headers.parse_header_line(header_line.as_bytes()) {
                    Ok(_) | Err(RequestError::UnsupportedHeader) => continue,
                    Err(e) => return Err(e),
                };
            }
            return Ok(headers);
        }
        Err(RequestError::InvalidRequest)
    }
}

/// Wrapper over supported Media Types.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MediaType {
    /// Media Type: "text/plain".
    PlainText,
    /// Media Type: "application/json".
    ApplicationJson,
}

impl Default for MediaType {
    /// Default value for MediaType is application/json
    fn default() -> Self {
        Self::ApplicationJson
    }
}

impl MediaType {
    /// Parses a byte slice into a MediaType structure for a HTTP request. MediaType
    /// must be ASCII, so also UTF-8 valid.
    ///
    /// # Errors
    /// The function returns `InvalidRequest` when parsing the byte stream fails or
    /// unsupported MediaType found.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::MediaType;
    ///
    /// assert!(MediaType::try_from(b"application/json").is_ok());
    /// assert!(MediaType::try_from(b"application/json2").is_err());
    /// ```
    pub fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
        if bytes.is_empty() {
            return Err(RequestError::InvalidRequest);
        }
        let utf8_slice =
            String::from_utf8(bytes.to_vec()).map_err(|_| RequestError::InvalidRequest)?;
        match utf8_slice.as_str().trim() {
            "text/plain" => Ok(Self::PlainText),
            "application/json" => Ok(Self::ApplicationJson),
            _ => Err(RequestError::InvalidRequest),
        }
    }

    /// Returns a static string representation of the object.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::MediaType;
    ///
    /// let media_type = MediaType::ApplicationJson;
    /// assert_eq!(media_type.as_str(), "application/json");
    /// ```
    pub fn as_str(self) -> &'static str {
        match self {
            Self::PlainText => "text/plain",
            Self::ApplicationJson => "application/json",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    impl Headers {
        pub fn new(content_length: i32, expect: bool, chunked: bool) -> Self {
            Self {
                content_length,
                expect,
                chunked,
            }
        }
    }

    #[test]
    fn test_default() {
        let headers = Headers::default();
        assert_eq!(headers.content_length(), 0);
        assert_eq!(headers.chunked(), false);
        assert_eq!(headers.expect(), false);
    }

    #[test]
    fn test_try_from_media() {
        assert_eq!(
            MediaType::try_from(b"application/json").unwrap(),
            MediaType::ApplicationJson
        );

        assert_eq!(
            MediaType::try_from(b"text/plain").unwrap(),
            MediaType::PlainText
        );

        assert_eq!(
            MediaType::try_from(b"").unwrap_err(),
            RequestError::InvalidRequest
        );

        assert_eq!(
            MediaType::try_from(b"application/json-patch").unwrap_err(),
            RequestError::InvalidRequest
        );
    }

    #[test]
    fn test_media_as_str() {
        let media_type = MediaType::ApplicationJson;
        assert_eq!(media_type.as_str(), "application/json");

        let media_type = MediaType::PlainText;
        assert_eq!(media_type.as_str(), "text/plain");
    }

    #[test]
    fn test_try_from_headers() {
        // Valid headers.
        assert_eq!(
            Headers::try_from(
                b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: 55\r\n\r\n"
            )
            .unwrap()
            .content_length,
            55
        );

        let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
        // Invalid headers.
        assert!(Headers::try_from(&bytes[..]).is_err());
    }

    #[test]
    fn test_parse_header_line() {
        let mut header = Headers::default();

        // Invalid header syntax.
        assert_eq!(
            header.parse_header_line(b"Expect"),
            Err(RequestError::InvalidHeader)
        );

        // Invalid content length.
        assert_eq!(
            header.parse_header_line(b"Content-Length: five"),
            Err(RequestError::InvalidHeader)
        );

        // Invalid transfer encoding.
        assert_eq!(
            header.parse_header_line(b"Transfer-Encoding: gzip"),
            Err(RequestError::UnsupportedHeader)
        );

        // Invalid expect.
        assert_eq!(
            header
                .parse_header_line(b"Expect: 102-processing")
                .unwrap_err(),
            RequestError::InvalidHeader
        );

        // Unsupported media type.
        assert_eq!(
            header
                .parse_header_line(b"Content-Type: application/json-patch")
                .unwrap_err(),
            RequestError::UnsupportedHeader
        );

        // Invalid input format.
        let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
        assert_eq!(
            header.parse_header_line(&input[..]).unwrap_err(),
            RequestError::InvalidHeader
        );

        // Test valid transfer encoding.
        assert!(header
            .parse_header_line(b"Transfer-Encoding: chunked")
            .is_ok());
        assert!(header.chunked());

        // Test valid expect.
        assert!(header.parse_header_line(b"Expect: 100-continue").is_ok());
        assert!(header.expect());

        // Test valid media type.
        assert!(header
            .parse_header_line(b"Content-Type: application/json")
            .is_ok());
    }

    #[test]
    fn test_header_try_from() {
        // Bad header.
        assert_eq!(
            Header::try_from(b"Encoding").unwrap_err(),
            RequestError::InvalidHeader
        );

        // Invalid encoding.
        let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
        assert_eq!(
            Header::try_from(&input[..]).unwrap_err(),
            RequestError::InvalidRequest
        );

        // Test valid headers.
        let header = Header::try_from(b"Expect").unwrap();
        assert_eq!(header.raw(), b"Expect");

        let header = Header::try_from(b"Transfer-Encoding").unwrap();
        assert_eq!(header.raw(), b"Transfer-Encoding");

        let header = Header::try_from(b"content-length").unwrap();
        assert_eq!(header.raw(), b"Content-Length");
    }
}
<commit_msg>micro_http: add support for accept headers<commit_after>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::result::Result;

use RequestError;

/// Wrapper over an HTTP Header type.
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum Header {
    /// Header `Content-Length`.
    ContentLength,
    /// Header `Content-Type`.
    ContentType,
    /// Header `Expect`.
    Expect,
    /// Header `Transfer-Encoding`.
    TransferEncoding,
    /// Header `Server`.
    Server,
    /// Header `Accept`
    Accept,
}

impl Header {
    /// Returns a byte slice representation of the object.
    pub fn raw(&self) -> &'static [u8] {
        match self {
            Self::ContentLength => b"Content-Length",
            Self::ContentType => b"Content-Type",
            Self::Expect => b"Expect",
            Self::TransferEncoding => b"Transfer-Encoding",
            Self::Server => b"Server",
            Self::Accept => b"Accept",
        }
    }

    /// Parses a byte slice into a Header structure. Header must be ASCII, so also
    /// UTF-8 valid.
    ///
    /// # Errors
    /// `InvalidRequest` is returned if slice contains invalid utf8 characters.
    /// `InvalidHeader` is returned if unsupported header found.
    fn try_from(string: &[u8]) -> Result<Self, RequestError> {
        if let Ok(mut utf8_string) = String::from_utf8(string.to_vec()) {
            utf8_string.make_ascii_lowercase();
            match utf8_string.trim() {
                "content-length" => Ok(Self::ContentLength),
                "content-type" => Ok(Self::ContentType),
                "expect" => Ok(Self::Expect),
                "transfer-encoding" => Ok(Self::TransferEncoding),
                "server" => Ok(Self::Server),
                "accept" => Ok(Self::Accept),
                _ => Err(RequestError::InvalidHeader),
            }
        } else {
            Err(RequestError::InvalidRequest)
        }
    }
}

/// Wrapper over the list of headers associated with a Request that we need
/// in order to parse the request correctly and be able to respond to it.
///
/// The only `Content-Type`s supported are `text/plain` and `application/json`, which are both
/// in plain text actually and don't influence our parsing process.
///
/// All the other possible header fields are not necessary in order to serve this connection
/// and, thus, are not of interest to us. However, we still look for header fields that might
/// invalidate our request as we don't support the full set of HTTP/1.1 specification.
/// Such header entries are "Transfer-Encoding: identity; q=0", which means a compression
/// algorithm is applied to the body of the request, or "Expect: 103-checkpoint".
#[derive(Debug)]
pub struct Headers {
    /// The `Content-Length` header field tells us how many bytes we need to receive
    /// from the source after the headers.
    content_length: i32,
    /// The `Expect` header field is set when the headers contain the entry "Expect: 100-continue".
    /// This means that, per HTTP/1.1 specifications, we must send a response with the status code
    /// 100 after we have received the headers in order to receive the body of the request. This
    /// field should be known immediately after parsing the headers.
    expect: bool,
    /// `Chunked` is a possible value of the `Transfer-Encoding` header field and every HTTP/1.1
    /// server must support it. It is useful only when receiving the body of the request and should
    /// be known immediately after parsing the headers.
    chunked: bool,
    /// `Accept` header might be used by HTTP clients to enforce server responses with content
    /// formatted in a specific way.
    accept: MediaType,
}

impl Default for Headers {
    /// By default Requests are created with no headers.
    fn default() -> Self {
        Self {
            content_length: Default::default(),
            expect: Default::default(),
            chunked: Default::default(),
            // The default `Accept` media type is plain text. This is inclusive enough
            // for structured and unstructured text.
            accept: MediaType::PlainText,
        }
    }
}

impl Headers {
    /// Expects one header line and parses it, updating the header structure or returning an
    /// error if the header is invalid.
    ///
    /// # Errors
    /// `UnsupportedHeader` is returned when the parsed header line is not of interest
    /// to us or when it is unrecognizable.
    /// `InvalidHeader` is returned when the parsed header is formatted incorrectly or suggests
    /// that the client is using HTTP features that we do not support in this implementation,
    /// which invalidates the request.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::Headers;
    ///
    /// let mut request_header = Headers::default();
    /// assert!(request_header.parse_header_line(b"Content-Length: 24").is_ok());
    /// assert!(request_header.parse_header_line(b"Content-Length: 24: 2").is_err());
    /// ```
    pub fn parse_header_line(&mut self, header_line: &[u8]) -> Result<(), RequestError> {
        // Headers must be ASCII, so also UTF-8 valid.
        match std::str::from_utf8(header_line) {
            Ok(headers_str) => {
                let entry = headers_str.split(": ").collect::<Vec<&str>>();
                if entry.len() != 2 {
                    return Err(RequestError::InvalidHeader);
                }
                if let Ok(head) = Header::try_from(entry[0].as_bytes()) {
                    match head {
                        Header::ContentLength => match entry[1].trim().parse::<i32>() {
                            Ok(content_length) => {
                                self.content_length = content_length;
                                Ok(())
                            }
                            Err(_) => Err(RequestError::InvalidHeader),
                        },
                        Header::ContentType => {
                            match MediaType::try_from(entry[1].trim().as_bytes()) {
                                Ok(_) => Ok(()),
                                Err(_) => Err(RequestError::UnsupportedHeader),
                            }
                        }
                        Header::Accept => match MediaType::try_from(entry[1].trim().as_bytes()) {
                            Ok(accept_type) => {
                                self.accept = accept_type;
                                Ok(())
                            }
                            Err(_) => Err(RequestError::UnsupportedHeader),
                        },
                        Header::TransferEncoding => match entry[1].trim() {
                            "chunked" => {
                                self.chunked = true;
                                Ok(())
                            }
                            "identity; q=0" => Err(RequestError::InvalidHeader),
                            _ => Err(RequestError::UnsupportedHeader),
                        },
                        Header::Expect => match entry[1].trim() {
                            "100-continue" => {
                                self.expect = true;
                                Ok(())
                            }
                            _ => Err(RequestError::InvalidHeader),
                        },
                        Header::Server => Ok(()),
                    }
                } else {
                    Err(RequestError::UnsupportedHeader)
                }
            }
            _ => Err(RequestError::InvalidHeader),
        }
    }

    /// Returns the content length of the body.
    pub fn content_length(&self) -> i32 {
        self.content_length
    }

    /// Returns `true` if the transfer encoding is chunked.
    #[allow(unused)]
    pub fn chunked(&self) -> bool {
        self.chunked
    }

    /// Returns `true` if the client is expecting the code 100.
    #[allow(unused)]
    pub fn expect(&self) -> bool {
        self.expect
    }

    /// Returns the `Accept` header `MediaType`.
    pub fn accept(&self) -> MediaType {
        self.accept
    }

    /// Parses a byte slice into a Headers structure for a HTTP request.
    ///
    /// The byte slice is expected to have the following format: </br>
    ///     * Request Header Lines "<header_line> CRLF"- Optional </br>
    /// There can be any number of request headers, including none, followed by
    /// an extra sequence of Carriage Return and Line Feed.
    /// All header fields are parsed. However, only the ones present in the
    /// [`Headers`](struct.Headers.html) struct are relevant to us and stored
    /// for future use.
    ///
    /// # Errors
    /// The function returns `InvalidHeader` when parsing the byte stream fails.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::Headers;
    ///
    /// let request_headers = Headers::try_from(b"Content-Length: 55\r\n\r\n");
    /// ```
    pub fn try_from(bytes: &[u8]) -> Result<Headers, RequestError> {
        // Headers must be ASCII, so also UTF-8 valid.
        if let Ok(text) = std::str::from_utf8(bytes) {
            let mut headers = Self::default();

            let header_lines = text.split("\r\n");
            for header_line in header_lines {
                if header_line.is_empty() {
                    break;
                }
                match headers.parse_header_line(header_line.as_bytes()) {
                    Ok(_) | Err(RequestError::UnsupportedHeader) => continue,
                    Err(e) => return Err(e),
                };
            }
            return Ok(headers);
        }
        Err(RequestError::InvalidRequest)
    }

    /// Accept header setter.
    pub fn set_accept(&mut self, media_type: MediaType) {
        self.accept = media_type;
    }
}

/// Wrapper over supported Media Types.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MediaType {
    /// Media Type: "text/plain".
    PlainText,
    /// Media Type: "application/json".
    ApplicationJson,
}

impl Default for MediaType {
    /// Default value for MediaType is application/json
    fn default() -> Self {
        Self::ApplicationJson
    }
}

impl MediaType {
    /// Parses a byte slice into a MediaType structure for a HTTP request. MediaType
    /// must be ASCII, so also UTF-8 valid.
    ///
    /// # Errors
    /// The function returns `InvalidRequest` when parsing the byte stream fails or
    /// unsupported MediaType found.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::MediaType;
    ///
    /// assert!(MediaType::try_from(b"application/json").is_ok());
    /// assert!(MediaType::try_from(b"application/json2").is_err());
    /// ```
    pub fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
        if bytes.is_empty() {
            return Err(RequestError::InvalidRequest);
        }
        let utf8_slice =
            String::from_utf8(bytes.to_vec()).map_err(|_| RequestError::InvalidRequest)?;
        match utf8_slice.as_str().trim() {
            "text/plain" => Ok(Self::PlainText),
            "application/json" => Ok(Self::ApplicationJson),
            _ => Err(RequestError::InvalidRequest),
        }
    }

    /// Returns a static string representation of the object.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate micro_http;
    /// use micro_http::MediaType;
    ///
    /// let media_type = MediaType::ApplicationJson;
    /// assert_eq!(media_type.as_str(), "application/json");
    /// ```
    pub fn as_str(self) -> &'static str {
        match self {
            Self::PlainText => "text/plain",
            Self::ApplicationJson => "application/json",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    impl Headers {
        pub fn new(content_length: i32, expect: bool, chunked: bool) -> Self {
            Self {
                content_length,
                expect,
                chunked,
                accept: MediaType::PlainText,
            }
        }
    }

    #[test]
    fn test_default() {
        let headers = Headers::default();
        assert_eq!(headers.content_length(), 0);
        assert_eq!(headers.chunked(), false);
        assert_eq!(headers.expect(), false);
    }

    #[test]
    fn test_try_from_media() {
        assert_eq!(
            MediaType::try_from(b"application/json").unwrap(),
            MediaType::ApplicationJson
        );

        assert_eq!(
            MediaType::try_from(b"text/plain").unwrap(),
            MediaType::PlainText
        );

        assert_eq!(
            MediaType::try_from(b"").unwrap_err(),
            RequestError::InvalidRequest
        );

        assert_eq!(
            MediaType::try_from(b"application/json-patch").unwrap_err(),
            RequestError::InvalidRequest
        );
    }

    #[test]
    fn test_media_as_str() {
        let media_type = MediaType::ApplicationJson;
        assert_eq!(media_type.as_str(), "application/json");

        let media_type = MediaType::PlainText;
        assert_eq!(media_type.as_str(), "text/plain");
    }

    #[test]
    fn test_try_from_headers() {
        // Valid headers.
        let headers =  Headers::try_from(
            b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nAccept: application/json\r\nContent-Length: 55\r\n\r\n"
        )
            .unwrap();
        assert_eq!(headers.content_length, 55);
        assert_eq!(headers.accept, MediaType::ApplicationJson);

        // Valid headers.
        let headers =  Headers::try_from(
            b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nAccept: text/plain\r\nContent-Length: 49\r\n\r\n"
        )
            .unwrap();
        assert_eq!(headers.content_length, 49);
        assert_eq!(headers.accept, MediaType::PlainText);

        // Valid headers.
        let headers = Headers::try_from(
            b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: 29\r\n\r\n",
        )
        .unwrap();
        assert_eq!(headers.content_length, 29);

        let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
        // Invalid headers.
        assert!(Headers::try_from(&bytes[..]).is_err());
    }

    #[test]
    fn test_parse_header_line() {
        let mut header = Headers::default();

        // Invalid header syntax.
        assert_eq!(
            header.parse_header_line(b"Expect"),
            Err(RequestError::InvalidHeader)
        );

        // Invalid content length.
        assert_eq!(
            header.parse_header_line(b"Content-Length: five"),
            Err(RequestError::InvalidHeader)
        );

        // Invalid transfer encoding.
        assert_eq!(
            header.parse_header_line(b"Transfer-Encoding: gzip"),
            Err(RequestError::UnsupportedHeader)
        );

        // Invalid expect.
        assert_eq!(
            header
                .parse_header_line(b"Expect: 102-processing")
                .unwrap_err(),
            RequestError::InvalidHeader
        );

        // Unsupported media type.
        assert_eq!(
            header
                .parse_header_line(b"Content-Type: application/json-patch")
                .unwrap_err(),
            RequestError::UnsupportedHeader
        );

        // Invalid input format.
        let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
        assert_eq!(
            header.parse_header_line(&input[..]).unwrap_err(),
            RequestError::InvalidHeader
        );

        // Test valid transfer encoding.
        assert!(header
            .parse_header_line(b"Transfer-Encoding: chunked")
            .is_ok());
        assert!(header.chunked());

        // Test valid expect.
        assert!(header.parse_header_line(b"Expect: 100-continue").is_ok());
        assert!(header.expect());

        // Test valid media type.
        assert!(header
            .parse_header_line(b"Content-Type: application/json")
            .is_ok());

        // Test valid accept media type.
        assert!(header
            .parse_header_line(b"Accept: application/json")
            .is_ok());
        assert!(header.accept == MediaType::ApplicationJson);
        assert!(header.parse_header_line(b"Accept: text/plain").is_ok());
        assert!(header.accept == MediaType::PlainText);

        // Test invalid accept media type.
        assert!(header
            .parse_header_line(b"Accept: application/json-patch")
            .is_err());
    }

    #[test]
    fn test_header_try_from() {
        // Bad header.
        assert_eq!(
            Header::try_from(b"Encoding").unwrap_err(),
            RequestError::InvalidHeader
        );

        // Invalid encoding.
        let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
        assert_eq!(
            Header::try_from(&input[..]).unwrap_err(),
            RequestError::InvalidRequest
        );

        // Test valid headers.
        let header = Header::try_from(b"Expect").unwrap();
        assert_eq!(header.raw(), b"Expect");

        let header = Header::try_from(b"Transfer-Encoding").unwrap();
        assert_eq!(header.raw(), b"Transfer-Encoding");

        let header = Header::try_from(b"content-length").unwrap();
        assert_eq!(header.raw(), b"Content-Length");

        let header = Header::try_from(b"Accept").unwrap();
        assert_eq!(header.raw(), b"Accept");
    }
}
<|endoftext|>