<commit_before>
extern crate num_traits;

use self::num_traits::{Num, Signed, One, Zero};

use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::fmt;
use std::fs;

#[derive(Copy, Clone)]
pub struct BBox(i64,i64,i64,i64);// xmin, xmax, ymin, ymax
impl BBox {
    pub fn new() -> BBox {
        BBox(0,0,0,0)
    }

    pub fn xmin(&self) -> i64 { self.0 }
    pub fn xmax(&self) -> i64 { self.1 }
    pub fn ymin(&self) -> i64 { self.2 }
    pub fn ymax(&self) -> i64 { self.3 }

    pub fn update(&mut self, x: i64, y: i64) {
        if x < self.0 { self.0 = x; }
        if x > self.1 { self.1 = x; }
        if y < self.2 { self.2 = y; }
        if y > self.3 { self.3 = y; }
    }
}
impl fmt::Display for BBox {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "BBox: {}, {}, {}, {}", self.0, self.1, self.2, self.3)
    }
}

#[derive(Copy, Clone, Debug)]
pub enum Direction { North, South, East, West }
const ALL_DIRECTIONS: [Direction; 4] = [Direction::North, Direction::South, Direction::East, Direction::West];
impl Direction {
    pub fn from_xy<T: Num + Signed + Zero>(x: T, y: T) -> Option<Direction> {
        if x.is_zero() && y.is_positive() { return Some(Direction::North); }
        if x.is_zero() && y.is_negative() { return Some(Direction::South); }
        if y.is_zero() && x.is_positive() { return Some(Direction::East); }
        if y.is_zero() && x.is_negative() { return Some(Direction::West); }
        return None
    }

    pub fn each() -> &'static [Direction] { &ALL_DIRECTIONS }

    pub fn step<T: Num + One>(&self, x: T, y: T) -> (T, T) {
        match self {
            Direction::North => (x, y + T::one()),
            Direction::South => (x, y - T::one()),
            Direction::East  => (x + T::one(), y),
            Direction::West  => (x - T::one(), y),
        }
    }

    // xy: y-axis north is positive
    pub fn xy(&self) -> (i64, i64) {
        match self {
            Direction::North => (0, 1),
            Direction::South => (0, -1),
            Direction::East  => (1, 0),
            Direction::West  => (-1, 0),
        }
    }

    // xY: y-axis inversion
    pub fn xY(&self) -> (i64, i64) {
        match self {
            Direction::North => (0, -1),
            Direction::South => (0, 1),
            Direction::East  => (1, 0),
            Direction::West  => (-1, 0),
        }
    }

    pub fn rev(&self) -> Direction {
        match self {
            Direction::North => Direction::South,
            Direction::South => Direction::North,
            Direction::East  => Direction::West,
            Direction::West  => Direction::East,
        }
    }
    pub fn left(&self) -> Direction {
        match self {
            Direction::North => Direction::West,
            Direction::South => Direction::East,
            Direction::East  => Direction::North,
            Direction::West  => Direction::South,
        }
    }
    pub fn right(&self) -> Direction {
        match self {
            Direction::North => Direction::East,
            Direction::South => Direction::West,
            Direction::East  => Direction::South,
            Direction::West  => Direction::North,
        }
    }
}
impl fmt::Display for Direction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", match self {
            Direction::North => "N",
            Direction::South => "S",
            Direction::East  => "E",
            Direction::West  => "W",
        })
    }
}

#[derive(Clone)]
pub struct Robot {
    pos: (i64, i64),
    dir: Direction,
}
impl Robot {
    pub fn new() -> Robot {
        Robot { pos: (0,0), dir: Direction::North }
    }

    pub fn pos(&self) -> (i64, i64) { self.pos }
    pub fn set_pos(&mut self, x: i64, y: i64) { self.pos = (x, y); }

    pub fn direction(&mut self) -> Direction { self.dir }
    pub fn set_direction(&mut self, new: Direction) { self.dir = new; }

    pub fn step(&mut self, n: i64) {
        let (x, y) = self.dir.xy();
        self.pos.0 = self.pos.0.checked_add(x.checked_mul(n).unwrap_or_else(|| panic!("X Mul Overflow"))).unwrap_or_else(|| panic!("X Add Overflow"));
        self.pos.1 = self.pos.1.checked_add(y.checked_mul(n).unwrap_or_else(|| panic!("Y Mul Overflow"))).unwrap_or_else(|| panic!("Y Add Overflow"));
    }
}

#[derive(Clone)]
pub struct Chart {
    pub map: HashMap<(i64, i64), char>,
    pub bbox: BBox,
    pub portals: HashMap<(i64, i64), (i64, i64)>,
    pub portal_symbol: char,
    pub print_flipped: bool,
}
impl Chart {
    pub fn new() -> Chart {
        Chart {
            map: HashMap::new(),
            bbox: BBox::new(),
            portal_symbol: '^',
            portals: HashMap::new(),
            print_flipped: false,
        }
    }

    pub fn load(fname: &str) -> Chart {
        let mut chart = Chart::new();
        let contents = fs::read_to_string(fname)
            .unwrap_or_else(|err| panic!("Error reading {}: {}", fname, err));

        for (j, line) in contents.trim_end_matches('\n').split('\n').enumerate() {
            for (i, ch) in line.chars().enumerate() {
                chart.put(i as i64, j as i64, ch);
            }
        }
        return chart;
    }

    pub fn count(&self, wanted: impl Fn(i64, i64, char) -> bool) -> i64 {
        let mut count = 0;
        for ((x, y), ch) in self.map.iter() {
            if wanted(*x, *y, *ch) {
                count += 1;
            }
        }
        return count;
    }

    pub fn count_at(&self, x: i64, y: i64, ch: char) -> i64 {
        match self.map.get(&(x,y)) {
            Some(c) => if *c == ch { 1 } else { 0 },
            None    => 0,
        }
    }

    pub fn item_at(&self, x: i64, y: i64) -> char {
        match self.map.get(&(x,y)) {
            Some(x) => *x,
            None    => ' ',
        }
    }

    pub fn locate(&self, ch: char) -> Option<(i64, i64)> {
        for (key, val) in self.map.iter() {
            if *val == ch {
                return Some(*key);
            }
        }
        return None;
    }

    pub fn follow_portal(&self, x0: i64, y0: i64) -> (i64, i64) {
        match self.portals.get(&(x0,y0)) {
            Some(&(x, y)) => (x,y),
            None    => panic!("Tried to follow non-existant portal at ({}, {})", x0, y0),
        }
    }
    pub fn add_portal(&mut self, x0: i64, y0: i64, x1: i64, y1: i64) {
        let sym = self.portal_symbol;
        self.put(x0, y0, sym);
        self.portals.insert((x0, y0), (x1, y1));
    }

    pub fn get(&self, x: i64, y: i64) -> Option<&char> { self.map.get(&(x,y)) }
    pub fn put(&mut self, x: i64, y: i64, obj: char) {
        self.bbox.update(x, y);
        self.map.insert((x, y), obj);
    }

    pub fn shortest_path(&self, x: i64, y: i64, valid: impl Fn(i64, i64, char) -> bool, wanted: impl Fn(i64, i64, char) -> bool) -> Option<(i64, i64, Vec<Direction>)> {
        let mut todo: VecDeque<(i64, i64, Vec<Direction>)> = VecDeque::new();
        let mut seen: HashSet<(i64, i64)> = HashSet::new();

        todo.push_back((x, y, Vec::new()));
        loop {
            let (x, y, path) = todo.pop_front()?; // or return None
            let spot = self.item_at(x, y);
            if wanted(x, y, spot) {
                return Some((x, y, path));
            }
            if valid(x, y, spot) {
                for dir in Direction::each() {
                    let (mut a, mut b) = dir.step(x, y);
                    if self.item_at(a, b) == self.portal_symbol {
                        let (aa, bb) = self.follow_portal(a, b);
                        a = aa; b = bb;
                    }
                    if !seen.contains(&(a, b)) {
                        let mut newpath = path.clone();
                        newpath.push(*dir);
                        todo.push_back((a, b, newpath));
                    }
                }
            }
            seen.insert((x, y));
        };
    }

}
impl fmt::Display for Chart {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.print_flipped {
            for y in self.bbox.ymin()..=self.bbox.ymax() {
                for x in self.bbox.xmin()..=self.bbox.xmax() {
                    write!(f, "{}", self.item_at(x, y))?;
                }
                write!(f, "{}", "\n")?;
            }
        } else {
            for y in (self.bbox.ymin()..=self.bbox.ymax()).rev() {
                for x in self.bbox.xmin()..=self.bbox.xmax() {
                    write!(f, "{}", self.item_at(x, y))?;
                }
                write!(f, "{}", "\n")?;
            }
        }
        Ok(())
    }
}
<commit_msg>remove unused method which raises warnings<commit_after>
extern crate num_traits;

use self::num_traits::{Num, Signed, One, Zero};

use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::fmt;
use std::fs;

#[derive(Copy, Clone)]
pub struct BBox(i64,i64,i64,i64);// xmin, xmax, ymin, ymax
impl BBox {
    pub fn new() -> BBox {
        BBox(0,0,0,0)
    }

    pub fn xmin(&self) -> i64 { self.0 }
    pub fn xmax(&self) -> i64 { self.1 }
    pub fn ymin(&self) -> i64 { self.2 }
    pub fn ymax(&self) -> i64 { self.3 }

    pub fn update(&mut self, x: i64, y: i64) {
        if x < self.0 { self.0 = x; }
        if x > self.1 { self.1 = x; }
        if y < self.2 { self.2 = y; }
        if y > self.3 { self.3 = y; }
    }
}
impl fmt::Display for BBox {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "BBox: {}, {}, {}, {}", self.0, self.1, self.2, self.3)
    }
}

#[derive(Copy, Clone, Debug)]
pub enum Direction { North, South, East, West }
const ALL_DIRECTIONS: [Direction; 4] = [Direction::North, Direction::South, Direction::East, Direction::West];
impl Direction {
    pub fn from_xy<T: Num + Signed + Zero>(x: T, y: T) -> Option<Direction> {
        if x.is_zero() && y.is_positive() { return Some(Direction::North); }
        if x.is_zero() && y.is_negative() { return Some(Direction::South); }
        if y.is_zero() && x.is_positive() { return Some(Direction::East); }
        if y.is_zero() && x.is_negative() { return Some(Direction::West); }
        return None
    }

    pub fn each() -> &'static [Direction] { &ALL_DIRECTIONS }

    pub fn step<T: Num + One>(&self, x: T, y: T) -> (T, T) {
        match self {
            Direction::North => (x, y + T::one()),
            Direction::South => (x, y - T::one()),
            Direction::East  => (x + T::one(), y),
            Direction::West  => (x - T::one(), y),
        }
    }

    // xy: y-axis north is positive
    pub fn xy(&self) -> (i64, i64) {
        match self {
            Direction::North => (0, 1),
            Direction::South => (0, -1),
            Direction::East  => (1, 0),
            Direction::West  => (-1, 0),
        }
    }

    pub fn rev(&self) -> Direction {
        match self {
            Direction::North => Direction::South,
            Direction::South => Direction::North,
            Direction::East  => Direction::West,
            Direction::West  => Direction::East,
        }
    }
    pub fn left(&self) -> Direction {
        match self {
            Direction::North => Direction::West,
            Direction::South => Direction::East,
            Direction::East  => Direction::North,
            Direction::West  => Direction::South,
        }
    }
    pub fn right(&self) -> Direction {
        match self {
            Direction::North => Direction::East,
            Direction::South => Direction::West,
            Direction::East  => Direction::South,
            Direction::West  => Direction::North,
        }
    }
}
impl fmt::Display for Direction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", match self {
            Direction::North => "N",
            Direction::South => "S",
            Direction::East  => "E",
            Direction::West  => "W",
        })
    }
}

#[derive(Clone)]
pub struct Robot {
    pos: (i64, i64),
    dir: Direction,
}
impl Robot {
    pub fn new() -> Robot {
        Robot { pos: (0,0), dir: Direction::North }
    }

    pub fn pos(&self) -> (i64, i64) { self.pos }
    pub fn set_pos(&mut self, x: i64, y: i64) { self.pos = (x, y); }

    pub fn direction(&mut self) -> Direction { self.dir }
    pub fn set_direction(&mut self, new: Direction) { self.dir = new; }

    pub fn step(&mut self, n: i64) {
        let (x, y) = self.dir.xy();
        self.pos.0 = self.pos.0.checked_add(x.checked_mul(n).unwrap_or_else(|| panic!("X Mul Overflow"))).unwrap_or_else(|| panic!("X Add Overflow"));
        self.pos.1 = self.pos.1.checked_add(y.checked_mul(n).unwrap_or_else(|| panic!("Y Mul Overflow"))).unwrap_or_else(|| panic!("Y Add Overflow"));
    }
}

#[derive(Clone)]
pub struct Chart {
    pub map: HashMap<(i64, i64), char>,
    pub bbox: BBox,
    pub portals: HashMap<(i64, i64), (i64, i64)>,
    pub portal_symbol: char,
    pub print_flipped: bool,
}
impl Chart {
    pub fn new() -> Chart {
        Chart {
            map: HashMap::new(),
            bbox: BBox::new(),
            portal_symbol: '^',
            portals: HashMap::new(),
            print_flipped: false,
        }
    }

    pub fn load(fname: &str) -> Chart {
        let mut chart = Chart::new();
        let contents = fs::read_to_string(fname)
            .unwrap_or_else(|err| panic!("Error reading {}: {}", fname, err));

        for (j, line) in contents.trim_end_matches('\n').split('\n').enumerate() {
            for (i, ch) in line.chars().enumerate() {
                chart.put(i as i64, j as i64, ch);
            }
        }
        return chart;
    }

    pub fn count(&self, wanted: impl Fn(i64, i64, char) -> bool) -> i64 {
        let mut count = 0;
        for ((x, y), ch) in self.map.iter() {
            if wanted(*x, *y, *ch) {
                count += 1;
            }
        }
        return count;
    }

    pub fn count_at(&self, x: i64, y: i64, ch: char) -> i64 {
        match self.map.get(&(x,y)) {
            Some(c) => if *c == ch { 1 } else { 0 },
            None    => 0,
        }
    }

    pub fn item_at(&self, x: i64, y: i64) -> char {
        match self.map.get(&(x,y)) {
            Some(x) => *x,
            None    => ' ',
        }
    }

    pub fn locate(&self, ch: char) -> Option<(i64, i64)> {
        for (key, val) in self.map.iter() {
            if *val == ch {
                return Some(*key);
            }
        }
        return None;
    }

    pub fn follow_portal(&self, x0: i64, y0: i64) -> (i64, i64) {
        match self.portals.get(&(x0,y0)) {
            Some(&(x, y)) => (x,y),
            None    => panic!("Tried to follow non-existant portal at ({}, {})", x0, y0),
        }
    }
    pub fn add_portal(&mut self, x0: i64, y0: i64, x1: i64, y1: i64) {
        let sym = self.portal_symbol;
        self.put(x0, y0, sym);
        self.portals.insert((x0, y0), (x1, y1));
    }

    pub fn get(&self, x: i64, y: i64) -> Option<&char> { self.map.get(&(x,y)) }
    pub fn put(&mut self, x: i64, y: i64, obj: char) {
        self.bbox.update(x, y);
        self.map.insert((x, y), obj);
    }

    pub fn shortest_path(&self, x: i64, y: i64, valid: impl Fn(i64, i64, char) -> bool, wanted: impl Fn(i64, i64, char) -> bool) -> Option<(i64, i64, Vec<Direction>)> {
        let mut todo: VecDeque<(i64, i64, Vec<Direction>)> = VecDeque::new();
        let mut seen: HashSet<(i64, i64)> = HashSet::new();

        todo.push_back((x, y, Vec::new()));
        loop {
            let (x, y, path) = todo.pop_front()?; // or return None
            let spot = self.item_at(x, y);
            if wanted(x, y, spot) {
                return Some((x, y, path));
            }
            if valid(x, y, spot) {
                for dir in Direction::each() {
                    let (mut a, mut b) = dir.step(x, y);
                    if self.item_at(a, b) == self.portal_symbol {
                        let (aa, bb) = self.follow_portal(a, b);
                        a = aa; b = bb;
                    }
                    if !seen.contains(&(a, b)) {
                        let mut newpath = path.clone();
                        newpath.push(*dir);
                        todo.push_back((a, b, newpath));
                    }
                }
            }
            seen.insert((x, y));
        };
    }

}
impl fmt::Display for Chart {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.print_flipped {
            for y in self.bbox.ymin()..=self.bbox.ymax() {
                for x in self.bbox.xmin()..=self.bbox.xmax() {
                    write!(f, "{}", self.item_at(x, y))?;
                }
                write!(f, "{}", "\n")?;
            }
        } else {
            for y in (self.bbox.ymin()..=self.bbox.ymax()).rev() {
                for x in self.bbox.xmin()..=self.bbox.xmax() {
                    write!(f, "{}", self.item_at(x, y))?;
                }
                write!(f, "{}", "\n")?;
            }
        }
        Ok(())
    }
}
<|endoftext|>


<commit_before>extern crate breakpad_symbols;
extern crate disasm;
extern crate env_logger;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate log;
extern crate minidump;
extern crate minidump_common;
extern crate reqwest;
#[macro_use]
extern crate structopt;

use breakpad_symbols::{SimpleFrame, HttpSymbolSupplier, Symbolizer};
use disasm::{Color, CpuArch, SourceLocation, SourceLookup};
use failure::Error;
use minidump_common::traits::Module;
use minidump::{Minidump, MinidumpException, MinidumpMemoryList, MinidumpModule, MinidumpModuleList,
               MinidumpSystemInfo};
use minidump::system_info::CPU;
use reqwest::Client;
use std::env;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(name = "get-minidump-instructions", about = "Display instructions from a minidump")]
struct GetMinidumpInstructions {
    #[structopt(long = "color", help = "Enable colored output")]
    color: Option<Color>,
    #[structopt(help = "Input minidump", parse(from_os_str))]
    minidump: PathBuf,
    #[structopt(help = "Symbol paths", parse(from_os_str))]
    symbol_paths: Vec<PathBuf>,
}

#[derive(StructOpt)]
#[structopt(name = "dumpstack", about = "Display possible stack frames from a minidump")]
struct DumpStack {
    #[structopt(help = "Input minidump", parse(from_os_str))]
    minidump: PathBuf,
    #[structopt(help = "Symbol paths", parse(from_os_str))]
    symbol_paths: Vec<PathBuf>,
    #[structopt(short = "a", help = "Show stack addresses that don't resolve to functions")]
    all: bool,
}

struct SymLookup {
    modules: MinidumpModuleList,
    symbolizer: Symbolizer,
    client: Client,
}

struct HgWebFile {
    host: String,
    repo: String,
    rev: String,
    path: String,
}

struct GitHubFile {
    repo: String,
    rev: String,
    path: String,
}

trait VCSFile {
    fn raw_url(&self) -> String;
    fn annotate_url(&self, line: u64) -> String;
    fn as_local_filename(&self) -> String;
}

impl VCSFile for HgWebFile {
    fn raw_url(&self) -> String {
        format!("https://{}/{}/raw-file/{}/{}",
                self.host, self.repo, self.rev, self.path)
    }

    fn annotate_url(&self, line: u64) -> String {
        format!("https://{}/{}/annotate/{}/{}#l{}",
                self.host, self.repo, self.rev, self.path, line)
    }
    fn as_local_filename(&self) -> String {
        format!("{}_{}_{}", self.host, self.rev, self.path).replace('/', "_")
    }
}

impl VCSFile for GitHubFile {
    fn raw_url(&self) -> String {
        format!("http://raw.githubusercontent.com/{}/{}/{}", self.repo, self.rev, self.path)
    }
    fn annotate_url(&self, line: u64) -> String {
        format!("https://github.com/{}/blob/{}/{}#L{}", self.repo, self.rev, self.path, line)
    }
    fn as_local_filename(&self) -> String {
        format!("{}_{}_{}", self.repo, self.rev, self.path).replace('/', "_")
    }
}

fn file_exists(path: &Path) -> bool {
    fs::metadata(path).is_ok()
}

fn fetch_url_to_path(client: &Client, url: &str, path: &Path) -> Result<(), Error> {
    debug!("fetch_url_to_path({}, {:?})", url, path);
    let mut res = client.get(url).send()?.error_for_status()?;
    debug!("fetch_url_to_path: HTTP success");
    let mut tmp_path = path.to_owned().into_os_string();
    tmp_path.push(".tmp");
    {
        let mut f = File::create(&tmp_path)?;
        res.copy_to(&mut f)?;
    }
    fs::rename(&tmp_path, &path)?;
    Ok(())
}

fn parse_vcs_info(filename: &str) -> Result<Box<VCSFile>, Error> {
    let mut bits = filename.split(':');
    Ok(match (bits.next(), bits.next(), bits.next(), bits.next()) {
        (Some("hg"), Some(repo), Some(path), Some(rev)) if repo.starts_with("hg.mozilla.org/") => {
            let mut s = repo.splitn(2, '/');
            let host = s.next().unwrap().to_owned();
            let repo = s.next().unwrap().to_owned();
            let path = path.to_owned();
            let rev = rev.to_owned();
            Box::new(HgWebFile {
                host,
                repo,
                rev,
                path,
            })
        }
        (Some("git"), Some(repo), Some(path), Some(rev)) if repo.starts_with("github.com/") => {
            let repo = repo.splitn(2, '/').nth(1).unwrap().to_owned();
            let path = path.to_owned();
            let rev = rev.to_owned();
            Box::new(GitHubFile {
                repo,
                rev,
                path,
            })
        }
        _ => return Err(format_err!("No VCS info in filename")),
    })
}

fn maybe_fetch_source_file(client: &Client, url: &str, path: &Path) -> Result<(), Error> {
    if file_exists(path) {
        Ok(())
    } else {
        fetch_url_to_path(client, url, path)
    }
}

impl SourceLookup for SymLookup {
    fn lookup(&mut self, address: u64) -> Option<SourceLocation> {
        self.modules.module_at_address(address).and_then(|module| {
            let mut frame = SimpleFrame::with_instruction(address);
            self.symbolizer.fill_symbol(module, &mut frame);
            let SimpleFrame { source_file, source_line, .. } = frame;
            if let (Some(file), Some(line)) = (source_file, source_line) {
                let line = line as u64;
                match parse_vcs_info(&file) {
                    Ok(info) => {
                        let url = info.raw_url();
                        let local = env::temp_dir().join(info.as_local_filename());
                        if maybe_fetch_source_file(&self.client, &url, &local).is_ok() {
                            return Some(SourceLocation {
                                file: local.to_string_lossy().into_owned(),
                                file_display: Some(info.annotate_url(line)),
                                line
                            })
                        }
                    }
                    _ => {}
                }
                // If fetching the file doesn't work, just hand back the original filename.
                Some(SourceLocation { file: file.into(), file_display: None, line })
            } else {
                // We didn't find any source info.
                None
            }
        })
    }
}

fn handle_symbol_paths(symbol_paths: Vec<PathBuf>) -> Result<Symbolizer, Error> {
    let tmp_path = env::temp_dir().join("symbols");
    fs::create_dir_all(&tmp_path)?;
    let (symbol_paths, symbol_urls) = if symbol_paths.is_empty() {
        // Use the Mozilla symbol server if no symbol paths are supplied.
        let symbol_urls = vec!["https://symbols.mozilla.org/".to_owned()];
        (symbol_paths, symbol_urls)
    } else {
        let urls = symbol_paths.iter()
            .filter(|p| p.starts_with("http"))
            .filter_map(|p| p.to_str().map(str::to_owned)).collect();
        let paths = symbol_paths.into_iter().filter(|p| !p.starts_with("http")).collect();
        (paths, urls)
    };
    let supplier = HttpSymbolSupplier::new(symbol_urls, tmp_path, symbol_paths);
    Ok(Symbolizer::new(supplier))
}

pub fn get_minidump_instructions() -> Result<(), Error> {
    env_logger::init();
    let GetMinidumpInstructions { color, minidump, symbol_paths } =
        GetMinidumpInstructions::from_args();
    let dump = Minidump::read_path(&minidump)?;
    let modules = dump.get_stream::<MinidumpModuleList>()?;
    let exception = dump.get_stream::<MinidumpException>()?;
    let context = exception.context.as_ref().ok_or(format_err!("Missing exception context"))?;
    let ip = context.get_instruction_pointer();
    let memory_list = dump.get_stream::<MinidumpMemoryList>()?;
    let memory = memory_list.memory_at_address(ip)
        .ok_or(format_err!("Minidump doesn't contain a memory region that contains the instruction pointer from the exception record"))?;
    let sys_info = dump.get_stream::<MinidumpSystemInfo>()?;
    let arch = match sys_info.cpu {
        CPU::X86 => CpuArch::X86,
        CPU::X86_64 => CpuArch::X86_64,
        _ => return Err(format_err!("Unsupported CPU architecture: {}", sys_info.cpu)),
    };
    let symbolizer = handle_symbol_paths(symbol_paths)?;
    let client = Client::new();
    let mut lookup = SymLookup { modules, symbolizer, client };
    println!("Faulting instruction pointer: {:#x}", ip);
    info!("Disassembling {} bytes starting at {:#x} of arch {:?}",
          memory.bytes.len(), memory.base_address, arch);
    disasm::disasm_bytes(&memory.bytes,
                         memory.base_address,
                         arch,
                         color.unwrap_or(Color::Auto),
                         Some(ip),
                         &mut lookup)?;
    Ok(())
}

    fn basename(f: &str) -> &str {
        match f.rfind(|c| c == '/' || c == '\\') {
            None => f,
            Some(index) => &f[(index + 1)..],
        }
    }

fn print_frame(module: &MinidumpModule, frame: SimpleFrame) {
    print!("{}", basename(&module.code_file()));
    if let Some(ref func) = frame.function {
        print!("!{}", func);
        if let (Some(ref file), Some(line)) = (frame.source_file, frame.source_line) {
            print!(" [{} : {}", basename(file), line);
            if let Some(line_base) = frame.source_line_base {
                print!(" + {}", frame.instruction - line_base);
            }
            print!("]");
        } else if let Some(func_base) = frame.function_base {
            print!(" + {:#x}", frame.instruction - func_base);
        }
    } else {
        print!(" + {:#x}", frame.instruction - module.base_address());
    }
    println!("");
}

pub fn dump_minidump_stack() -> Result<(), Error> {
    env_logger::init();
    let DumpStack { all, minidump, symbol_paths } =
        DumpStack::from_args();
    info!("Reading minidump {:?}", minidump);
    let dump = Minidump::read_path(&minidump)?;
    let symbolizer = handle_symbol_paths(symbol_paths)?;
    let modules = dump.get_stream::<MinidumpModuleList>()?;
    let memory_list = dump.get_stream::<MinidumpMemoryList>()?;
    let sys_info = dump.get_stream::<MinidumpSystemInfo>()?;
    let wordsize = match sys_info.cpu {
        CPU::X86 | CPU::PPC | CPU::Sparc | CPU::ARM => 4,
        CPU::X86_64 | CPU::PPC64 | CPU::ARM64 => 8,
        CPU::Unknown(u) => bail!("Unknown cpu: {:#x}", u),
    };
    // TODO: provide a commandline option for the address.
    // Default to the top of the crashing stack.
    let exception = dump.get_stream::<MinidumpException>()?;
    let context = exception.context.as_ref().ok_or(format_err!("Missing exception context"))?;
    let sp = context.get_stack_pointer();
    info!("Stack pointer: {:#x}", sp);
    let memory = memory_list.memory_at_address(sp)
        .ok_or_else(|| format_err!("Minidump doesn't contain a memory region that contains address {:#x}", sp))?;
    for addr in (memory.base_address..memory.base_address + memory.size).step_by(wordsize) {
        let instruction = match wordsize {
            4 => memory.get_memory_at_address::<u32>(addr).map(|i| i as u64),
            8 => memory.get_memory_at_address::<u64>(addr),
            _ => unreachable!(),
        };
        if let Some(instruction) = instruction {
            if let Some(module) = modules.module_at_address(instruction) {
                let mut frame = SimpleFrame::with_instruction(instruction);
                symbolizer.fill_symbol(module, &mut frame);
                if frame.function.is_some() || all {
                    print!("{:#0width$x} ", addr, width=wordsize*2);
                    print_frame(module, frame);
                }
            }
        }
    }
    Ok(())
}
<commit_msg>Make dumpstack output URLs to source files when possible<commit_after>extern crate breakpad_symbols;
extern crate disasm;
extern crate env_logger;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate log;
extern crate minidump;
extern crate minidump_common;
extern crate reqwest;
#[macro_use]
extern crate structopt;

use breakpad_symbols::{SimpleFrame, HttpSymbolSupplier, Symbolizer};
use disasm::{Color, CpuArch, SourceLocation, SourceLookup};
use failure::Error;
use minidump_common::traits::Module;
use minidump::{Minidump, MinidumpException, MinidumpMemoryList, MinidumpModule, MinidumpModuleList,
               MinidumpSystemInfo};
use minidump::system_info::CPU;
use reqwest::Client;
use std::env;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(name = "get-minidump-instructions", about = "Display instructions from a minidump")]
struct GetMinidumpInstructions {
    #[structopt(long = "color", help = "Enable colored output")]
    color: Option<Color>,
    #[structopt(help = "Input minidump", parse(from_os_str))]
    minidump: PathBuf,
    #[structopt(help = "Symbol paths", parse(from_os_str))]
    symbol_paths: Vec<PathBuf>,
}

#[derive(StructOpt)]
#[structopt(name = "dumpstack", about = "Display possible stack frames from a minidump")]
struct DumpStack {
    #[structopt(help = "Input minidump", parse(from_os_str))]
    minidump: PathBuf,
    #[structopt(help = "Symbol paths", parse(from_os_str))]
    symbol_paths: Vec<PathBuf>,
    #[structopt(short = "a", help = "Show stack addresses that don't resolve to functions")]
    all: bool,
}

struct SymLookup {
    modules: MinidumpModuleList,
    symbolizer: Symbolizer,
    client: Client,
}

struct HgWebFile {
    host: String,
    repo: String,
    rev: String,
    path: String,
}

struct GitHubFile {
    repo: String,
    rev: String,
    path: String,
}

trait VCSFile {
    fn raw_url(&self) -> String;
    fn annotate_url(&self, line: u64) -> String;
    fn as_local_filename(&self) -> String;
}

impl VCSFile for HgWebFile {
    fn raw_url(&self) -> String {
        format!("https://{}/{}/raw-file/{}/{}",
                self.host, self.repo, self.rev, self.path)
    }

    fn annotate_url(&self, line: u64) -> String {
        format!("https://{}/{}/annotate/{}/{}#l{}",
                self.host, self.repo, self.rev, self.path, line)
    }
    fn as_local_filename(&self) -> String {
        format!("{}_{}_{}", self.host, self.rev, self.path).replace('/', "_")
    }
}

impl VCSFile for GitHubFile {
    fn raw_url(&self) -> String {
        format!("http://raw.githubusercontent.com/{}/{}/{}", self.repo, self.rev, self.path)
    }
    fn annotate_url(&self, line: u64) -> String {
        format!("https://github.com/{}/blob/{}/{}#L{}", self.repo, self.rev, self.path, line)
    }
    fn as_local_filename(&self) -> String {
        format!("{}_{}_{}", self.repo, self.rev, self.path).replace('/', "_")
    }
}

fn file_exists(path: &Path) -> bool {
    fs::metadata(path).is_ok()
}

fn fetch_url_to_path(client: &Client, url: &str, path: &Path) -> Result<(), Error> {
    debug!("fetch_url_to_path({}, {:?})", url, path);
    let mut res = client.get(url).send()?.error_for_status()?;
    debug!("fetch_url_to_path: HTTP success");
    let mut tmp_path = path.to_owned().into_os_string();
    tmp_path.push(".tmp");
    {
        let mut f = File::create(&tmp_path)?;
        res.copy_to(&mut f)?;
    }
    fs::rename(&tmp_path, &path)?;
    Ok(())
}

fn parse_vcs_info(filename: &str) -> Result<Box<VCSFile>, Error> {
    let mut bits = filename.split(':');
    Ok(match (bits.next(), bits.next(), bits.next(), bits.next()) {
        (Some("hg"), Some(repo), Some(path), Some(rev)) if repo.starts_with("hg.mozilla.org/") => {
            let mut s = repo.splitn(2, '/');
            let host = s.next().unwrap().to_owned();
            let repo = s.next().unwrap().to_owned();
            let path = path.to_owned();
            let rev = rev.to_owned();
            Box::new(HgWebFile {
                host,
                repo,
                rev,
                path,
            })
        }
        (Some("git"), Some(repo), Some(path), Some(rev)) if repo.starts_with("github.com/") => {
            let repo = repo.splitn(2, '/').nth(1).unwrap().to_owned();
            let path = path.to_owned();
            let rev = rev.to_owned();
            Box::new(GitHubFile {
                repo,
                rev,
                path,
            })
        }
        _ => return Err(format_err!("No VCS info in filename")),
    })
}

fn maybe_fetch_source_file(client: &Client, url: &str, path: &Path) -> Result<(), Error> {
    if file_exists(path) {
        Ok(())
    } else {
        fetch_url_to_path(client, url, path)
    }
}

impl SourceLookup for SymLookup {
    fn lookup(&mut self, address: u64) -> Option<SourceLocation> {
        self.modules.module_at_address(address).and_then(|module| {
            let mut frame = SimpleFrame::with_instruction(address);
            self.symbolizer.fill_symbol(module, &mut frame);
            let SimpleFrame { source_file, source_line, .. } = frame;
            if let (Some(file), Some(line)) = (source_file, source_line) {
                let line = line as u64;
                match parse_vcs_info(&file) {
                    Ok(info) => {
                        let url = info.raw_url();
                        let local = env::temp_dir().join(info.as_local_filename());
                        if maybe_fetch_source_file(&self.client, &url, &local).is_ok() {
                            return Some(SourceLocation {
                                file: local.to_string_lossy().into_owned(),
                                file_display: Some(info.annotate_url(line)),
                                line
                            })
                        }
                    }
                    _ => {}
                }
                // If fetching the file doesn't work, just hand back the original filename.
                Some(SourceLocation { file: file.into(), file_display: None, line })
            } else {
                // We didn't find any source info.
                None
            }
        })
    }
}

fn handle_symbol_paths(symbol_paths: Vec<PathBuf>) -> Result<Symbolizer, Error> {
    let tmp_path = env::temp_dir().join("symbols");
    fs::create_dir_all(&tmp_path)?;
    let (symbol_paths, symbol_urls) = if symbol_paths.is_empty() {
        // Use the Mozilla symbol server if no symbol paths are supplied.
        let symbol_urls = vec!["https://symbols.mozilla.org/".to_owned()];
        (symbol_paths, symbol_urls)
    } else {
        let urls = symbol_paths.iter()
            .filter(|p| p.starts_with("http"))
            .filter_map(|p| p.to_str().map(str::to_owned)).collect();
        let paths = symbol_paths.into_iter().filter(|p| !p.starts_with("http")).collect();
        (paths, urls)
    };
    let supplier = HttpSymbolSupplier::new(symbol_urls, tmp_path, symbol_paths);
    Ok(Symbolizer::new(supplier))
}

pub fn get_minidump_instructions() -> Result<(), Error> {
    env_logger::init();
    let GetMinidumpInstructions { color, minidump, symbol_paths } =
        GetMinidumpInstructions::from_args();
    let dump = Minidump::read_path(&minidump)?;
    let modules = dump.get_stream::<MinidumpModuleList>()?;
    let exception = dump.get_stream::<MinidumpException>()?;
    let context = exception.context.as_ref().ok_or(format_err!("Missing exception context"))?;
    let ip = context.get_instruction_pointer();
    let memory_list = dump.get_stream::<MinidumpMemoryList>()?;
    let memory = memory_list.memory_at_address(ip)
        .ok_or(format_err!("Minidump doesn't contain a memory region that contains the instruction pointer from the exception record"))?;
    let sys_info = dump.get_stream::<MinidumpSystemInfo>()?;
    let arch = match sys_info.cpu {
        CPU::X86 => CpuArch::X86,
        CPU::X86_64 => CpuArch::X86_64,
        _ => return Err(format_err!("Unsupported CPU architecture: {}", sys_info.cpu)),
    };
    let symbolizer = handle_symbol_paths(symbol_paths)?;
    let client = Client::new();
    let mut lookup = SymLookup { modules, symbolizer, client };
    println!("Faulting instruction pointer: {:#x}", ip);
    info!("Disassembling {} bytes starting at {:#x} of arch {:?}",
          memory.bytes.len(), memory.base_address, arch);
    disasm::disasm_bytes(&memory.bytes,
                         memory.base_address,
                         arch,
                         color.unwrap_or(Color::Auto),
                         Some(ip),
                         &mut lookup)?;
    Ok(())
}

    fn basename(f: &str) -> &str {
        match f.rfind(|c| c == '/' || c == '\\') {
            None => f,
            Some(index) => &f[(index + 1)..],
        }
    }

fn print_frame(module: &MinidumpModule, frame: SimpleFrame) {
    print!("{}", basename(&module.code_file()));
    if let Some(ref func) = frame.function {
        print!("!{}", func);
        if let (Some(ref file), Some(line)) = (frame.source_file, frame.source_line) {
            print!(" [");
            match parse_vcs_info(file) {
                Ok(info) => print!("{}", info.annotate_url(line as u64)),
                _ => print!("{} : {}", basename(file), line),
            }
            if let Some(line_base) = frame.source_line_base {
                print!(" + {}", frame.instruction - line_base);
            }
            print!("]");
        } else if let Some(func_base) = frame.function_base {
            print!(" + {:#x}", frame.instruction - func_base);
        }
    } else {
        print!(" + {:#x}", frame.instruction - module.base_address());
    }
    println!("");
}

pub fn dump_minidump_stack() -> Result<(), Error> {
    env_logger::init();
    let DumpStack { all, minidump, symbol_paths } =
        DumpStack::from_args();
    info!("Reading minidump {:?}", minidump);
    let dump = Minidump::read_path(&minidump)?;
    let symbolizer = handle_symbol_paths(symbol_paths)?;
    let modules = dump.get_stream::<MinidumpModuleList>()?;
    let memory_list = dump.get_stream::<MinidumpMemoryList>()?;
    let sys_info = dump.get_stream::<MinidumpSystemInfo>()?;
    let wordsize = match sys_info.cpu {
        CPU::X86 | CPU::PPC | CPU::Sparc | CPU::ARM => 4,
        CPU::X86_64 | CPU::PPC64 | CPU::ARM64 => 8,
        CPU::Unknown(u) => bail!("Unknown cpu: {:#x}", u),
    };
    // TODO: provide a commandline option for the address.
    // Default to the top of the crashing stack.
    let exception = dump.get_stream::<MinidumpException>()?;
    let context = exception.context.as_ref().ok_or(format_err!("Missing exception context"))?;
    let sp = context.get_stack_pointer();
    info!("Stack pointer: {:#x}", sp);
    let memory = memory_list.memory_at_address(sp)
        .ok_or_else(|| format_err!("Minidump doesn't contain a memory region that contains address {:#x}", sp))?;
    for addr in (memory.base_address..memory.base_address + memory.size).step_by(wordsize) {
        let instruction = match wordsize {
            4 => memory.get_memory_at_address::<u32>(addr).map(|i| i as u64),
            8 => memory.get_memory_at_address::<u64>(addr),
            _ => unreachable!(),
        };
        if let Some(instruction) = instruction {
            if let Some(module) = modules.module_at_address(instruction) {
                let mut frame = SimpleFrame::with_instruction(instruction);
                symbolizer.fill_symbol(module, &mut frame);
                if frame.function.is_some() || all {
                    print!("{:#0width$x} ", addr, width=wordsize*2);
                    print_frame(module, frame);
                }
            }
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>use num::Signed;
use std::cmp::PartialOrd;

use allocator::Allocator;
use ::{Real, Scalar};
use storage::{Storage, StorageMut};
use base::{DefaultAllocator, Matrix, Dim, MatrixMN};
use constraint::{SameNumberOfRows, SameNumberOfColumns, ShapeConstraint};

// FIXME: this should be be a trait on alga?
/// A trait for abstract matrix norms.
///
/// This may be moved to the alga crate in the future.
pub trait Norm<N: Scalar> {
    /// Apply this norm to the given matrix.
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C>;
    /// Use the metric induced by this norm to compute the metric distance between the two given matrices.
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2>;
}

/// Euclidean norm.
pub struct EuclideanNorm;
/// Lp norm.
pub struct LpNorm(pub i32);
/// L-infinite norm aka. Chebytchev norm aka. uniform norm aka. suppremum norm.
pub struct UniformNorm;

impl<N: Real> Norm<N> for EuclideanNorm {
    #[inline]
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C> {
        m.norm_squared().sqrt()
    }

    #[inline]
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2> {
        m1.zip_fold(m2, N::zero(), |acc, a, b| {
            let diff = a - b;
            acc + diff * diff
        }).sqrt()
    }
}

impl<N: Real> Norm<N> for LpNorm {
    #[inline]
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C> {
        m.fold(N::zero(), |a, b| {
            a + b.abs().powi(self.0)
        }).powf(::convert(1.0 / (self.0 as f64)))
    }

    #[inline]
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2> {
        m1.zip_fold(m2, N::zero(), |acc, a, b| {
            let diff = a - b;
            acc + diff.abs().powi(self.0)
        }).powf(::convert(1.0 / (self.0 as f64)))
    }
}

impl<N: Scalar + PartialOrd + Signed> Norm<N> for UniformNorm {
    #[inline]
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C> {
        m.amax()
    }

    #[inline]
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2> {
        m1.zip_fold(m2, N::zero(), |acc, a, b| {
            let val = (a - b).abs();
            if val > acc {
                val
            } else {
                acc
            }
        })
    }
}

impl<N: Real, R: Dim, C: Dim, S: Storage<N, R, C>> Matrix<N, R, C, S> {
    /// The squared L2 norm of this vector.
    #[inline]
    pub fn norm_squared(&self) -> N {
        let mut res = N::zero();

        for i in 0..self.ncols() {
            let col = self.column(i);
            res += col.dot(&col)
        }

        res
    }

    /// The L2 norm of this matrix.
    ///
    /// Use `.apply_norm` to apply a custom norm.
    #[inline]
    pub fn norm(&self) -> N {
        self.norm_squared().sqrt()
    }

    /// Compute the distance between `self` and `rhs` using the metric induced by the euclidean norm.
    ///
    /// Use `.apply_metric_distance` to apply a custom norm.
    #[inline]
    pub fn metric_distance<R2, C2, S2>(&self, rhs: &Matrix<N, R2, C2, S2>) -> N
        where R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R, R2> + SameNumberOfColumns<C, C2> {
        self.apply_metric_distance(rhs, &EuclideanNorm)
    }

    /// Uses the given `norm` to compute the norm of `self`.
    #[inline]
    pub fn apply_norm(&self, norm: &impl Norm<N>) -> N {
        norm.norm(self)
    }

    /// Uses the metric induced by the given `norm` to compute the metric distance between `self` and `rhs`.
    #[inline]
    pub fn apply_metric_distance<R2, C2, S2>(&self, rhs: &Matrix<N, R2, C2, S2>, norm: &impl Norm<N>) -> N
        where R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R, R2> + SameNumberOfColumns<C, C2> {
        norm.metric_distance(self,rhs)
    }

    /// The Lp norm of this matrix.
    #[inline]
    pub fn lp_norm(&self, p: i32) -> N {
        self.apply_norm(&LpNorm(p))
    }

    /// A synonym for the norm of this matrix.
    ///
    /// Aka the length.
    ///
    /// This function is simply implemented as a call to `norm()`
    #[inline]
    pub fn magnitude(&self) -> N {
        self.norm()
    }

    /// A synonym for the squared norm of this matrix.
    ///
    /// Aka the squared length.
    ///
    /// This function is simply implemented as a call to `norm_squared()`
    #[inline]
    pub fn magnitude_squared(&self) -> N {
        self.norm_squared()
    }

    /// Returns a normalized version of this matrix.
    #[inline]
    pub fn normalize(&self) -> MatrixMN<N, R, C>
        where DefaultAllocator: Allocator<N, R, C> {
        self / self.norm()
    }

    /// Returns a normalized version of this matrix unless its norm as smaller or equal to `eps`.
    #[inline]
    pub fn try_normalize(&self, min_norm: N) -> Option<MatrixMN<N, R, C>>
        where DefaultAllocator: Allocator<N, R, C> {
        let n = self.norm();

        if n <= min_norm {
            None
        } else {
            Some(self / n)
        }
    }
}

impl<N: Real, R: Dim, C: Dim, S: StorageMut<N, R, C>> Matrix<N, R, C, S> {
    /// Normalizes this matrix in-place and returns its norm.
    #[inline]
    pub fn normalize_mut(&mut self) -> N {
        let n = self.norm();
        *self /= n;

        n
    }

    /// Normalizes this matrix in-place or does nothing if its norm is smaller or equal to `eps`.
    ///
    /// If the normalization succeeded, returns the old normal of this matrix.
    #[inline]
    pub fn try_normalize_mut(&mut self, min_norm: N) -> Option<N> {
        let n = self.norm();

        if n <= min_norm {
            None
        } else {
            *self /= n;
            Some(n)
        }
    }
}
<commit_msg>Add doc-tests for apply_norm and apply_metric_distance.<commit_after>use num::Signed;
use std::cmp::PartialOrd;

use allocator::Allocator;
use ::{Real, Scalar};
use storage::{Storage, StorageMut};
use base::{DefaultAllocator, Matrix, Dim, MatrixMN};
use constraint::{SameNumberOfRows, SameNumberOfColumns, ShapeConstraint};

// FIXME: this should be be a trait on alga?
/// A trait for abstract matrix norms.
///
/// This may be moved to the alga crate in the future.
pub trait Norm<N: Scalar> {
    /// Apply this norm to the given matrix.
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C>;
    /// Use the metric induced by this norm to compute the metric distance between the two given matrices.
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2>;
}

/// Euclidean norm.
pub struct EuclideanNorm;
/// Lp norm.
pub struct LpNorm(pub i32);
/// L-infinite norm aka. Chebytchev norm aka. uniform norm aka. suppremum norm.
pub struct UniformNorm;

impl<N: Real> Norm<N> for EuclideanNorm {
    #[inline]
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C> {
        m.norm_squared().sqrt()
    }

    #[inline]
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2> {
        m1.zip_fold(m2, N::zero(), |acc, a, b| {
            let diff = a - b;
            acc + diff * diff
        }).sqrt()
    }
}

impl<N: Real> Norm<N> for LpNorm {
    #[inline]
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C> {
        m.fold(N::zero(), |a, b| {
            a + b.abs().powi(self.0)
        }).powf(::convert(1.0 / (self.0 as f64)))
    }

    #[inline]
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2> {
        m1.zip_fold(m2, N::zero(), |acc, a, b| {
            let diff = a - b;
            acc + diff.abs().powi(self.0)
        }).powf(::convert(1.0 / (self.0 as f64)))
    }
}

impl<N: Scalar + PartialOrd + Signed> Norm<N> for UniformNorm {
    #[inline]
    fn norm<R, C, S>(&self, m: &Matrix<N, R, C, S>) -> N
        where R: Dim, C: Dim, S: Storage<N, R, C> {
        m.amax()
    }

    #[inline]
    fn metric_distance<R1, C1, S1, R2, C2, S2>(&self, m1: &Matrix<N, R1, C1, S1>, m2: &Matrix<N, R2, C2, S2>) -> N
        where R1: Dim, C1: Dim, S1: Storage<N, R1, C1>,
              R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2> {
        m1.zip_fold(m2, N::zero(), |acc, a, b| {
            let val = (a - b).abs();
            if val > acc {
                val
            } else {
                acc
            }
        })
    }
}

impl<N: Real, R: Dim, C: Dim, S: Storage<N, R, C>> Matrix<N, R, C, S> {
    /// The squared L2 norm of this vector.
    #[inline]
    pub fn norm_squared(&self) -> N {
        let mut res = N::zero();

        for i in 0..self.ncols() {
            let col = self.column(i);
            res += col.dot(&col)
        }

        res
    }

    /// The L2 norm of this matrix.
    ///
    /// Use `.apply_norm` to apply a custom norm.
    #[inline]
    pub fn norm(&self) -> N {
        self.norm_squared().sqrt()
    }

    /// Compute the distance between `self` and `rhs` using the metric induced by the euclidean norm.
    ///
    /// Use `.apply_metric_distance` to apply a custom norm.
    #[inline]
    pub fn metric_distance<R2, C2, S2>(&self, rhs: &Matrix<N, R2, C2, S2>) -> N
        where R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R, R2> + SameNumberOfColumns<C, C2> {
        self.apply_metric_distance(rhs, &EuclideanNorm)
    }

    /// Uses the given `norm` to compute the norm of `self`.
    ///
    /// # Example
    ///
    /// ```
    /// # use nalgebra::{Vector3, UniformNorm, LpNorm, EuclideanNorm};
    ///
    /// let v = Vector3::new(1.0, 2.0, 3.0);
    /// assert_eq!(v.apply_norm(&UniformNorm), 3.0);
    /// assert_eq!(v.apply_norm(&LpNorm(1)), 6.0);
    /// assert_eq!(v.apply_norm(&EuclideanNorm), v.norm());
    /// ```
    #[inline]
    pub fn apply_norm(&self, norm: &impl Norm<N>) -> N {
        norm.norm(self)
    }

    /// Uses the metric induced by the given `norm` to compute the metric distance between `self` and `rhs`.
    ///
    /// # Example
    ///
    /// ```
    /// # use nalgebra::{Vector3, UniformNorm, LpNorm, EuclideanNorm};
    ///
    /// let v1 = Vector3::new(1.0, 2.0, 3.0);
    /// let v2 = Vector3::new(10.0, 20.0, 30.0);
    ///
    /// assert_eq!(v1.apply_metric_distance(&v2, &UniformNorm), 27.0);
    /// assert_eq!(v1.apply_metric_distance(&v2, &LpNorm(1)), 27.0 + 18.0 + 9.0);
    /// assert_eq!(v1.apply_metric_distance(&v2, &EuclideanNorm), (v1 - v2).norm());
    /// ```
    #[inline]
    pub fn apply_metric_distance<R2, C2, S2>(&self, rhs: &Matrix<N, R2, C2, S2>, norm: &impl Norm<N>) -> N
        where R2: Dim, C2: Dim, S2: Storage<N, R2, C2>,
              ShapeConstraint: SameNumberOfRows<R, R2> + SameNumberOfColumns<C, C2> {
        norm.metric_distance(self,rhs)
    }

    /// The Lp norm of this matrix.
    #[inline]
    pub fn lp_norm(&self, p: i32) -> N {
        self.apply_norm(&LpNorm(p))
    }

    /// A synonym for the norm of this matrix.
    ///
    /// Aka the length.
    ///
    /// This function is simply implemented as a call to `norm()`
    #[inline]
    pub fn magnitude(&self) -> N {
        self.norm()
    }

    /// A synonym for the squared norm of this matrix.
    ///
    /// Aka the squared length.
    ///
    /// This function is simply implemented as a call to `norm_squared()`
    #[inline]
    pub fn magnitude_squared(&self) -> N {
        self.norm_squared()
    }

    /// Returns a normalized version of this matrix.
    #[inline]
    pub fn normalize(&self) -> MatrixMN<N, R, C>
        where DefaultAllocator: Allocator<N, R, C> {
        self / self.norm()
    }

    /// Returns a normalized version of this matrix unless its norm as smaller or equal to `eps`.
    #[inline]
    pub fn try_normalize(&self, min_norm: N) -> Option<MatrixMN<N, R, C>>
        where DefaultAllocator: Allocator<N, R, C> {
        let n = self.norm();

        if n <= min_norm {
            None
        } else {
            Some(self / n)
        }
    }
}

impl<N: Real, R: Dim, C: Dim, S: StorageMut<N, R, C>> Matrix<N, R, C, S> {
    /// Normalizes this matrix in-place and returns its norm.
    #[inline]
    pub fn normalize_mut(&mut self) -> N {
        let n = self.norm();
        *self /= n;

        n
    }

    /// Normalizes this matrix in-place or does nothing if its norm is smaller or equal to `eps`.
    ///
    /// If the normalization succeeded, returns the old normal of this matrix.
    #[inline]
    pub fn try_normalize_mut(&mut self, min_norm: N) -> Option<N> {
        let n = self.norm();

        if n <= min_norm {
            None
        } else {
            *self /= n;
            Some(n)
        }
    }
}
<|endoftext|>


<commit_before>//! Details of the `ruma_api` procedural macro.

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
    parse::{Parse, ParseStream},
    Token, Type,
};

pub(crate) mod attribute;
pub(crate) mod metadata;
pub(crate) mod request;
pub(crate) mod response;

use self::{metadata::Metadata, request::Request, response::Response};
use crate::util;

/// The result of processing the `ruma_api` macro, ready for output back to source code.
pub struct Api {
    /// The `metadata` section of the macro.
    metadata: Metadata,

    /// The `request` section of the macro.
    request: Request,

    /// The `response` section of the macro.
    response: Response,

    /// The `error` section of the macro.
    error_ty: TokenStream,
}

impl Parse for Api {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let ruma_api = util::import_ruma_api();

        let metadata: Metadata = input.parse()?;
        let request: Request = input.parse()?;
        let response: Response = input.parse()?;
        let error_ty = match input.parse::<ErrorType>() {
            Ok(err) => err.ty.to_token_stream(),
            Err(_) => quote! { #ruma_api::error::Void },
        };

        let newtype_body_field = request.newtype_body_field();
        if metadata.method == "GET" && (request.has_body_fields() || newtype_body_field.is_some()) {
            let mut combined_error: Option<syn::Error> = None;
            let mut add_error = |field| {
                let error = syn::Error::new_spanned(field, "GET endpoints can't have body fields");
                if let Some(combined_error_ref) = &mut combined_error {
                    combined_error_ref.combine(error);
                } else {
                    combined_error = Some(error);
                }
            };

            for field in request.body_fields() {
                add_error(field);
            }

            if let Some(field) = newtype_body_field {
                add_error(field);
            }

            return Err(combined_error.unwrap());
        }

        Ok(Self { metadata, request, response, error_ty })
    }
}

pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
    // Guarantee `ruma_api` is available and named something we can refer to.
    let ruma_api = util::import_ruma_api();
    let http = quote! { #ruma_api::exports::http };
    let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
    let serde_json = quote! { #ruma_api::exports::serde_json };

    let description = &api.metadata.description;
    let method = &api.metadata.method;
    // We don't (currently) use this literal as a literal in the generated code. Instead we just
    // put it into doc comments, for which the span information is irrelevant. So we can work
    // with only the literal's value from here on.
    let name = &api.metadata.name.value();
    let path = &api.metadata.path;
    let rate_limited: TokenStream = api
        .metadata
        .rate_limited
        .iter()
        .map(|r| {
            let attrs = &r.attrs;
            let value = &r.value;
            quote! {
                #( #attrs )*
                rate_limited: #value,
            }
        })
        .collect();
    let authentication: TokenStream = api
        .metadata
        .authentication
        .iter()
        .map(|r| {
            let attrs = &r.attrs;
            let value = &r.value;
            quote! {
                #( #attrs )*
                authentication: #ruma_api::AuthScheme::#value,
            }
        })
        .collect();

    let request_type = &api.request;
    let response_type = &api.response;

    let incoming_request_type =
        if api.request.contains_lifetimes() { quote!(IncomingRequest) } else { quote!(Request) };

    let extract_request_path = if api.request.has_path_fields() {
        quote! {
            let path_segments: ::std::vec::Vec<&::std::primitive::str> =
                request.uri().path()[1..].split('/').collect();
        }
    } else {
        TokenStream::new()
    };

    let (request_path_string, parse_request_path) =
        util::request_path_string_and_parse(&api.request, &api.metadata, &ruma_api);

    let request_query_string = util::build_query_string(&api.request, &ruma_api);
    let extract_request_query = util::extract_request_query(&api.request, &ruma_api);

    let parse_request_query = if let Some(field) = api.request.query_map_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");

        quote! {
            #field_name: request_query,
        }
    } else {
        api.request.request_init_query_fields()
    };

    let mut header_kvs = api.request.append_header_kvs();
    for auth in &api.metadata.authentication {
        if auth.value == "AccessToken" {
            let attrs = &auth.attrs;
            header_kvs.extend(quote! {
                #( #attrs )*
                req_headers.insert(
                    #http::header::AUTHORIZATION,
                    #http::header::HeaderValue::from_str(
                        &::std::format!(
                            "Bearer {}",
                            access_token.ok_or(
                                #ruma_api::error::IntoHttpError::NeedsAuthentication
                            )?
                        )
                    )?
                );
            });
        }
    }

    let extract_request_headers = if api.request.has_header_fields() {
        quote! {
            let headers = request.headers();
        }
    } else {
        TokenStream::new()
    };

    let extract_request_body =
        if api.request.has_body_fields() || api.request.newtype_body_field().is_some() {
            let body_lifetimes = if api.request.has_body_lifetimes() {
                // duplicate the anonymous lifetime as many times as needed
                let lifetimes =
                    std::iter::repeat(quote! { '_ }).take(api.request.body_lifetime_count());
                quote! { < #( #lifetimes, )* >}
            } else {
                TokenStream::new()
            };
            quote! {
                let request_body: <
                    RequestBody #body_lifetimes
                    as #ruma_serde::Outgoing
                >::Incoming = {
                    // If the request body is completely empty, pretend it is an empty JSON object
                    // instead. This allows requests with only optional body parameters to be
                    // deserialized in that case.
                    let json = match request.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api::try_deserialize!(request, #serde_json::from_slice(json))
                };
            }
        } else {
            TokenStream::new()
        };

    let parse_request_headers = if api.request.has_header_fields() {
        api.request.parse_headers_from_request()
    } else {
        TokenStream::new()
    };

    let request_body = util::build_request_body(&api.request, &ruma_api);
    let parse_request_body = util::parse_request_body(&api.request);

    let extract_response_headers = if api.response.has_header_fields() {
        quote! {
            let mut headers = response.headers().clone();
        }
    } else {
        TokenStream::new()
    };

    let typed_response_body_decl =
        if api.response.has_body_fields() || api.response.newtype_body_field().is_some() {
            quote! {
                let response_body: <
                    ResponseBody
                    as #ruma_serde::Outgoing
                >::Incoming = {
                    // If the reponse body is completely empty, pretend it is an empty JSON object
                    // instead. This allows reponses with only optional body parameters to be
                    // deserialized in that case.
                    let json = match response.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api::try_deserialize!(
                        response,
                        #serde_json::from_slice(json),
                    )
                };
            }
        } else {
            TokenStream::new()
        };

    let response_init_fields = api.response.init_fields();
    let serialize_response_headers = api.response.apply_header_fields();

    let body = api.response.to_body();

    let metadata_doc = format!("Metadata for the `{}` API endpoint.", name);
    let request_doc =
        format!("Data for a request to the `{}` API endpoint.\n\n{}", name, description.value());
    let response_doc = format!("Data in the response from the `{}` API endpoint.", name);

    let error = &api.error_ty;
    let request_lifetimes = api.request.combine_lifetimes();

    let non_auth_endpoint_impls: TokenStream = api
        .metadata
        .authentication
        .iter()
        .map(|auth| {
            if auth.value != "None" {
                TokenStream::new()
            } else {
                let attrs = &auth.attrs;
                quote! {
                    #( #attrs )*
                    #[automatically_derived]
                    #[cfg(feature = "client")]
                    impl #request_lifetimes #ruma_api::OutgoingNonAuthRequest
                        for Request #request_lifetimes
                        {}

                    #( #attrs )*
                    #[automatically_derived]
                    #[cfg(feature = "server")]
                    impl #ruma_api::IncomingNonAuthRequest for #incoming_request_type {}
                }
            }
        })
        .collect();

    Ok(quote! {
        #[doc = #request_doc]
        #request_type

        #[doc = #response_doc]
        #response_type

        #[automatically_derived]
        #[cfg(feature = "server")]
        impl ::std::convert::TryFrom<Response> for #http::Response<Vec<u8>> {
            type Error = #ruma_api::error::IntoHttpError;

            fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> {
                let mut resp_builder = #http::Response::builder()
                    .header(#http::header::CONTENT_TYPE, "application/json");

                let mut headers = resp_builder
                    .headers_mut()
                    .expect("`http::ResponseBuilder` is in unusable state");
                #serialize_response_headers

                // This cannot fail because we parse each header value
                // checking for errors as each value is inserted and
                // we only allow keys from the `http::header` module.
                let response = resp_builder.body(#body).unwrap();
                Ok(response)
            }
        }

        #[automatically_derived]
        #[cfg(feature = "client")]
        impl ::std::convert::TryFrom<#http::Response<Vec<u8>>> for Response {
            type Error = #ruma_api::error::FromHttpResponseError<#error>;

            fn try_from(
                response: #http::Response<Vec<u8>>,
            ) -> ::std::result::Result<Self, Self::Error> {
                if response.status().as_u16() < 400 {
                    #extract_response_headers

                    #typed_response_body_decl

                    Ok(Self {
                        #response_init_fields
                    })
                } else {
                    match <#error as #ruma_api::EndpointError>::try_from_response(response) {
                        Ok(err) => Err(#ruma_api::error::ServerError::Known(err).into()),
                        Err(response_err) => {
                            Err(#ruma_api::error::ServerError::Unknown(response_err).into())
                        }
                    }
                }
            }
        }

        #[doc = #metadata_doc]
        pub const METADATA: #ruma_api::Metadata = #ruma_api::Metadata {
            description: #description,
            method: #http::Method::#method,
            name: #name,
            path: #path,
            #rate_limited
            #authentication
        };

        #[automatically_derived]
        #[cfg(feature = "client")]
        impl #request_lifetimes #ruma_api::OutgoingRequest for Request #request_lifetimes {
            type EndpointError = #error;
            type IncomingResponse = <Response as #ruma_serde::Outgoing>::Incoming;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api::Metadata = self::METADATA;

            fn try_into_http_request(
                self,
                base_url: &::std::primitive::str,
                access_token: ::std::option::Option<&str>,
            ) -> ::std::result::Result<#http::Request<Vec<u8>>, #ruma_api::error::IntoHttpError> {
                let metadata = self::METADATA;

                let mut req_builder = #http::Request::builder()
                    .method(#http::Method::#method)
                    .uri(::std::format!(
                        "{}{}{}",
                        base_url.strip_suffix('/').unwrap_or(base_url),
                        #request_path_string,
                        #request_query_string,
                    ))
                    .header(#ruma_api::exports::http::header::CONTENT_TYPE, "application/json");

                let mut req_headers = req_builder
                    .headers_mut()
                    .expect("`http::RequestBuilder` is in unusable state");

                #header_kvs

                let http_request = req_builder.body(#request_body)?;

                Ok(http_request)
            }
        }

        #[automatically_derived]
        #[cfg(feature = "server")]
        impl #ruma_api::IncomingRequest for #incoming_request_type {
            type EndpointError = #error;
            type OutgoingResponse = Response;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api::Metadata = self::METADATA;

            fn try_from_http_request(
                request: #http::Request<Vec<u8>>
            ) -> ::std::result::Result<Self, #ruma_api::error::FromHttpRequestError> {
                #extract_request_path
                #extract_request_query
                #extract_request_headers
                #extract_request_body

                Ok(Self {
                    #parse_request_path
                    #parse_request_query
                    #parse_request_headers
                    #parse_request_body
                })
            }
        }

        #non_auth_endpoint_impls
    })
}

mod kw {
    syn::custom_keyword!(error);
}

pub struct ErrorType {
    pub error_kw: kw::error,
    pub ty: Type,
}

impl Parse for ErrorType {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let error_kw = input.parse::<kw::error>()?;
        input.parse::<Token![:]>()?;
        let ty = input.parse()?;

        Ok(Self { error_kw, ty })
    }
}
<commit_msg>api-macros: Refactor error type parsing<commit_after>//! Details of the `ruma_api` procedural macro.

use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    parse::{Parse, ParseStream},
    Token, Type,
};

pub(crate) mod attribute;
pub(crate) mod metadata;
pub(crate) mod request;
pub(crate) mod response;

use self::{metadata::Metadata, request::Request, response::Response};
use crate::util;

/// The result of processing the `ruma_api` macro, ready for output back to source code.
pub struct Api {
    /// The `metadata` section of the macro.
    metadata: Metadata,

    /// The `request` section of the macro.
    request: Request,

    /// The `response` section of the macro.
    response: Response,

    /// The `error` section of the macro.
    error_ty: Option<Type>,
}

mod kw {
    syn::custom_keyword!(error);
}

impl Parse for Api {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let metadata: Metadata = input.parse()?;
        let request: Request = input.parse()?;
        let response: Response = input.parse()?;

        // TODO: Use `bool::then` when MSRV >= 1.50
        let error_ty = if input.peek(kw::error) {
            let _: kw::error = input.parse()?;
            let _: Token![:] = input.parse()?;

            Some(input.parse()?)
        } else {
            None
        };

        let newtype_body_field = request.newtype_body_field();
        if metadata.method == "GET" && (request.has_body_fields() || newtype_body_field.is_some()) {
            let mut combined_error: Option<syn::Error> = None;
            let mut add_error = |field| {
                let error = syn::Error::new_spanned(field, "GET endpoints can't have body fields");
                if let Some(combined_error_ref) = &mut combined_error {
                    combined_error_ref.combine(error);
                } else {
                    combined_error = Some(error);
                }
            };

            for field in request.body_fields() {
                add_error(field);
            }

            if let Some(field) = newtype_body_field {
                add_error(field);
            }

            return Err(combined_error.unwrap());
        }

        Ok(Self { metadata, request, response, error_ty })
    }
}

pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
    // Guarantee `ruma_api` is available and named something we can refer to.
    let ruma_api = util::import_ruma_api();
    let http = quote! { #ruma_api::exports::http };
    let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
    let serde_json = quote! { #ruma_api::exports::serde_json };

    let description = &api.metadata.description;
    let method = &api.metadata.method;
    // We don't (currently) use this literal as a literal in the generated code. Instead we just
    // put it into doc comments, for which the span information is irrelevant. So we can work
    // with only the literal's value from here on.
    let name = &api.metadata.name.value();
    let path = &api.metadata.path;
    let rate_limited: TokenStream = api
        .metadata
        .rate_limited
        .iter()
        .map(|r| {
            let attrs = &r.attrs;
            let value = &r.value;
            quote! {
                #( #attrs )*
                rate_limited: #value,
            }
        })
        .collect();
    let authentication: TokenStream = api
        .metadata
        .authentication
        .iter()
        .map(|r| {
            let attrs = &r.attrs;
            let value = &r.value;
            quote! {
                #( #attrs )*
                authentication: #ruma_api::AuthScheme::#value,
            }
        })
        .collect();

    let request_type = &api.request;
    let response_type = &api.response;

    let incoming_request_type =
        if api.request.contains_lifetimes() { quote!(IncomingRequest) } else { quote!(Request) };

    let extract_request_path = if api.request.has_path_fields() {
        quote! {
            let path_segments: ::std::vec::Vec<&::std::primitive::str> =
                request.uri().path()[1..].split('/').collect();
        }
    } else {
        TokenStream::new()
    };

    let (request_path_string, parse_request_path) =
        util::request_path_string_and_parse(&api.request, &api.metadata, &ruma_api);

    let request_query_string = util::build_query_string(&api.request, &ruma_api);
    let extract_request_query = util::extract_request_query(&api.request, &ruma_api);

    let parse_request_query = if let Some(field) = api.request.query_map_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");

        quote! {
            #field_name: request_query,
        }
    } else {
        api.request.request_init_query_fields()
    };

    let mut header_kvs = api.request.append_header_kvs();
    for auth in &api.metadata.authentication {
        if auth.value == "AccessToken" {
            let attrs = &auth.attrs;
            header_kvs.extend(quote! {
                #( #attrs )*
                req_headers.insert(
                    #http::header::AUTHORIZATION,
                    #http::header::HeaderValue::from_str(
                        &::std::format!(
                            "Bearer {}",
                            access_token.ok_or(
                                #ruma_api::error::IntoHttpError::NeedsAuthentication
                            )?
                        )
                    )?
                );
            });
        }
    }

    let extract_request_headers = if api.request.has_header_fields() {
        quote! {
            let headers = request.headers();
        }
    } else {
        TokenStream::new()
    };

    let extract_request_body =
        if api.request.has_body_fields() || api.request.newtype_body_field().is_some() {
            let body_lifetimes = if api.request.has_body_lifetimes() {
                // duplicate the anonymous lifetime as many times as needed
                let lifetimes =
                    std::iter::repeat(quote! { '_ }).take(api.request.body_lifetime_count());
                quote! { < #( #lifetimes, )* >}
            } else {
                TokenStream::new()
            };
            quote! {
                let request_body: <
                    RequestBody #body_lifetimes
                    as #ruma_serde::Outgoing
                >::Incoming = {
                    // If the request body is completely empty, pretend it is an empty JSON object
                    // instead. This allows requests with only optional body parameters to be
                    // deserialized in that case.
                    let json = match request.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api::try_deserialize!(request, #serde_json::from_slice(json))
                };
            }
        } else {
            TokenStream::new()
        };

    let parse_request_headers = if api.request.has_header_fields() {
        api.request.parse_headers_from_request()
    } else {
        TokenStream::new()
    };

    let request_body = util::build_request_body(&api.request, &ruma_api);
    let parse_request_body = util::parse_request_body(&api.request);

    let extract_response_headers = if api.response.has_header_fields() {
        quote! {
            let mut headers = response.headers().clone();
        }
    } else {
        TokenStream::new()
    };

    let typed_response_body_decl =
        if api.response.has_body_fields() || api.response.newtype_body_field().is_some() {
            quote! {
                let response_body: <
                    ResponseBody
                    as #ruma_serde::Outgoing
                >::Incoming = {
                    // If the reponse body is completely empty, pretend it is an empty JSON object
                    // instead. This allows reponses with only optional body parameters to be
                    // deserialized in that case.
                    let json = match response.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api::try_deserialize!(
                        response,
                        #serde_json::from_slice(json),
                    )
                };
            }
        } else {
            TokenStream::new()
        };

    let response_init_fields = api.response.init_fields();
    let serialize_response_headers = api.response.apply_header_fields();

    let body = api.response.to_body();

    let metadata_doc = format!("Metadata for the `{}` API endpoint.", name);
    let request_doc =
        format!("Data for a request to the `{}` API endpoint.\n\n{}", name, description.value());
    let response_doc = format!("Data in the response from the `{}` API endpoint.", name);

    let error_ty =
        api.error_ty.map_or_else(|| quote! { #ruma_api::error::Void }, |err_ty| quote! { #err_ty });

    let request_lifetimes = api.request.combine_lifetimes();

    let non_auth_endpoint_impls: TokenStream = api
        .metadata
        .authentication
        .iter()
        .map(|auth| {
            if auth.value != "None" {
                TokenStream::new()
            } else {
                let attrs = &auth.attrs;
                quote! {
                    #( #attrs )*
                    #[automatically_derived]
                    #[cfg(feature = "client")]
                    impl #request_lifetimes #ruma_api::OutgoingNonAuthRequest
                        for Request #request_lifetimes
                        {}

                    #( #attrs )*
                    #[automatically_derived]
                    #[cfg(feature = "server")]
                    impl #ruma_api::IncomingNonAuthRequest for #incoming_request_type {}
                }
            }
        })
        .collect();

    Ok(quote! {
        #[doc = #request_doc]
        #request_type

        #[doc = #response_doc]
        #response_type

        #[automatically_derived]
        #[cfg(feature = "server")]
        impl ::std::convert::TryFrom<Response> for #http::Response<Vec<u8>> {
            type Error = #ruma_api::error::IntoHttpError;

            fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> {
                let mut resp_builder = #http::Response::builder()
                    .header(#http::header::CONTENT_TYPE, "application/json");

                let mut headers = resp_builder
                    .headers_mut()
                    .expect("`http::ResponseBuilder` is in unusable state");
                #serialize_response_headers

                // This cannot fail because we parse each header value
                // checking for errors as each value is inserted and
                // we only allow keys from the `http::header` module.
                let response = resp_builder.body(#body).unwrap();
                Ok(response)
            }
        }

        #[automatically_derived]
        #[cfg(feature = "client")]
        impl ::std::convert::TryFrom<#http::Response<Vec<u8>>> for Response {
            type Error = #ruma_api::error::FromHttpResponseError<#error_ty>;

            fn try_from(
                response: #http::Response<Vec<u8>>,
            ) -> ::std::result::Result<Self, Self::Error> {
                if response.status().as_u16() < 400 {
                    #extract_response_headers

                    #typed_response_body_decl

                    Ok(Self {
                        #response_init_fields
                    })
                } else {
                    match <#error_ty as #ruma_api::EndpointError>::try_from_response(response) {
                        Ok(err) => Err(#ruma_api::error::ServerError::Known(err).into()),
                        Err(response_err) => {
                            Err(#ruma_api::error::ServerError::Unknown(response_err).into())
                        }
                    }
                }
            }
        }

        #[doc = #metadata_doc]
        pub const METADATA: #ruma_api::Metadata = #ruma_api::Metadata {
            description: #description,
            method: #http::Method::#method,
            name: #name,
            path: #path,
            #rate_limited
            #authentication
        };

        #[automatically_derived]
        #[cfg(feature = "client")]
        impl #request_lifetimes #ruma_api::OutgoingRequest for Request #request_lifetimes {
            type EndpointError = #error_ty;
            type IncomingResponse = <Response as #ruma_serde::Outgoing>::Incoming;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api::Metadata = self::METADATA;

            fn try_into_http_request(
                self,
                base_url: &::std::primitive::str,
                access_token: ::std::option::Option<&str>,
            ) -> ::std::result::Result<#http::Request<Vec<u8>>, #ruma_api::error::IntoHttpError> {
                let metadata = self::METADATA;

                let mut req_builder = #http::Request::builder()
                    .method(#http::Method::#method)
                    .uri(::std::format!(
                        "{}{}{}",
                        base_url.strip_suffix('/').unwrap_or(base_url),
                        #request_path_string,
                        #request_query_string,
                    ))
                    .header(#ruma_api::exports::http::header::CONTENT_TYPE, "application/json");

                let mut req_headers = req_builder
                    .headers_mut()
                    .expect("`http::RequestBuilder` is in unusable state");

                #header_kvs

                let http_request = req_builder.body(#request_body)?;

                Ok(http_request)
            }
        }

        #[automatically_derived]
        #[cfg(feature = "server")]
        impl #ruma_api::IncomingRequest for #incoming_request_type {
            type EndpointError = #error_ty;
            type OutgoingResponse = Response;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api::Metadata = self::METADATA;

            fn try_from_http_request(
                request: #http::Request<Vec<u8>>
            ) -> ::std::result::Result<Self, #ruma_api::error::FromHttpRequestError> {
                #extract_request_path
                #extract_request_query
                #extract_request_headers
                #extract_request_body

                Ok(Self {
                    #parse_request_path
                    #parse_request_query
                    #parse_request_headers
                    #parse_request_body
                })
            }
        }

        #non_auth_endpoint_impls
    })
}
<|endoftext|>