<commit_before>use term_painter::ToStyle;
use term_painter::Color::*;
use std::io;

mod types;

// Game logic (board, initialization, valid move, set, play or finished)

/// Visualization of the boards.
fn print_boards(board1: &Vec<types::SubField>, board2: &Vec<types::SubField>) {
    println!("{}{}", "------------------ O W N   B O A R D ------------------",
        "------------------------- O P P O N E N T ----------------");

    let mut cnt = 9;

    for i in 0..10 {
        println!("");
        print!("{}  ", cnt);
        for j in 0..10 {
            if board1[j + i * 10] == types::SubField::Hit {
                print!(" {} ", Red.paint(board1[j + i * 10]));
            } else {
                print!(" {} ", board1[j + i * 10]);
            }
        }
        print!("        ");
        print!("{}  ", cnt);
        for j in 0..10 {
            if board2[j + i * 10] == types::SubField::Hit {
                print!(" {} ", Green.paint(board2[j + i * 10]));
            } else {
                print!(" {} ", board2[j + i * 10]);
            }
        }
        println!("");
        cnt -= 1;
    }
    println!("");
    println!("{}{}", "     A    B    C    D    E    F    G    H    I    J",
        "               A    B    C    D    E    F    G    H    I    J");
    println!("");
    println!("{}{}", "----------------------------------------------------",
        "--------------------------------------------------------------");
}

/// Reads a string from the terminal / user.
fn read_string() -> String {
    let mut buffer = String::new();
    io::stdin()
        .read_line(&mut buffer)
        .expect("something went horribly wrong...");

    /* Discard trailing newline */
    let new_len = buffer.trim_right().len();
    buffer.truncate(new_len);

    buffer
}

/// The actual placement of the ships.
fn place(player: &mut types::Player, ship: &types::ShipType) -> u8 {

    /* Vector to collect the indices for the possible ship position. */
    let mut indices = Vec::new();

    /* 100 --> invalid value */
    let mut input = 100;
    while input == 100 {
        input = get_input();
        match player.own_board[input] {
            types::SubField::Water => { indices.push(input); },
            _ => {
                println!("Invalid input, again please.");
                input = 100;
            },
        }
    }

    println!("{}{}", "Enter 'h' for a horizontal (upwards) orientation of the ship,",
        "'v' for a vertical (rightwards) one.");
    /* x --> invalid value */
    let mut orientation = "x".to_string();
    while orientation == "x" {
        orientation = read_string();
        if orientation != "h" && orientation != "v" {
            println!("Invalid input, again please.");
            orientation = "x".to_string();
        }
    }

    if orientation == "v" {
        for _ in 0..ship.size - 1 {
            /* Check to prevent out of bounds errors. */
            if input >= 10 {
                input -= 10;
            }
            if player.own_board[input] != types::SubField::Water
                /* input < 10 --> no field above */
                || input < 10 {
                    println!("Invalid position for this ship.");
                    println!("Please choose another coordinate.");
                    return 1
            }
            indices.push(input);
        }
    } else {
        for _ in 0..ship.size - 1 {
            /* Check to prevent out of bounds errors. */
            if input < 99 {
                input += 1;
            }
            if player.own_board[input] != types::SubField::Water
                /* unit position == 9 --> no field to the right */
                || (input % 10) == 9 {
                    println!("Invalid position for this ship.");
                    println!("Please choose another coordinate.");
                    return 1
            }
            indices.push(input);
        }
    }

    /* Places the ships on the board. */
    for i in indices.iter() {
        player.own_board[*i] = types::SubField::Ship;
    }

    0
}

/// Handles the initial ship placement for each player.
fn place_ships(mut player1: &mut types::Player, mut player2: &mut types::Player) {

    /* A vector of all the ships each player needs to place.
       The default version: #  Class of ship Size
                            1   Carrier       5
                            2   Battleship    4
                            3   Cruiser       3
                            4   Submarine     3
                            5   Destroyer     2
    */
    let s1 = types::ShipType{ name: "Carrier".to_string(), size: 5, amount: 1 };
    let s2 = types::ShipType{ name: "Battleship".to_string(), size: 4, amount: 2 };
    let s3 = types::ShipType{ name: "Cruiser".to_string(), size: 3, amount: 3 };
    let s4 = types::ShipType{ name: "Submarine".to_string(), size: 3, amount: 4 };
    let s5 = types::ShipType{ name: "Destroyer".to_string(), size: 2, amount: 5 };
    let ships = vec![s1, s2, s3, s4, s5];

    print_boards(&player1.own_board, &player1.op_board);

    /* Error code, should be replaced by proper error handling later. */
    let mut err;

    /* Asks player1 to place the ships. */
    for i in ships.iter() {
        for _ in 0..i.amount {
            err = 1;
            while err == 1 {
                println!("{}, please enter the first coordinate for your {:?}.",
                    player1.name, i.name);
                err = place(&mut player1, i);
            }
            player1.capacity += i.size;
            print_boards(&player1.own_board, &player1.op_board);
        }
    }

    /* Asks player2 to place the ships. */
    for i in ships.iter() {
        for _ in 0..i.amount {
            err = 1;
            while err == 1 {
                println!("{}, please enter the first coordinate for your {:?}.",
                    player2.name, i.name);
                err = place(&mut player2, i);
            }
            player2.capacity += i.size;
            print_boards(&player2.own_board, &player2.op_board);
        }
    }
}

/// Reads the coordinates of a field from the user
/// and returns the corresponding index.
fn get_input() -> usize {

    /* 100 --> invalid */
    let mut input = 100;

    while input == 100 {
        input = match read_string().as_ref() {
            "A0" => 90, "A1" => 80,"A2" => 70, "A3" => 60, "A4" => 50,
            "A5" => 40, "A6" => 30, "A7" => 20, "A8" => 10, "A9" => 0,
            "B0" => 91, "B1" => 81, "B2" => 71, "B3" => 61, "B4" => 51,
            "B5" => 41, "B6" => 31, "B7" => 21, "B8" => 11, "B9" => 1,
            "C0" => 92, "C1" => 82, "C2" => 72, "C3" => 62, "C4" => 52,
            "C5" => 42, "C6" => 32, "C7" => 22, "C8" => 12, "C9" => 2,
            "D0" => 93, "D1" => 83, "D2" => 73, "D3" => 63, "D4" => 53,
            "D5" => 43, "D6" => 33, "D7" => 23, "D8" => 13, "D9" => 3,
            "E0" => 94, "E1" => 84, "E2" => 74, "E3" => 64, "E4" => 54,
            "E5" => 44, "E6" => 34, "E7" => 24, "E8" => 14, "E9" => 4,
            "F0" => 95, "F1" => 85, "F2" => 75, "F3" => 65, "F4" => 55,
            "F5" => 45, "F6" => 35, "F7" => 25, "F8" => 15, "F9" => 5,
            "G0" => 96, "G1" => 86, "G2" => 76, "G3" => 66, "G4" => 56,
            "G5" => 46, "G6" => 36, "G7" => 26, "G8" => 16, "G9" => 6,
            "H0" => 97, "H1" => 87, "H2" => 77, "H3" => 67, "H4" => 57,
            "H5" => 47, "H6" => 37, "H7" => 27, "H8" => 17, "H9" => 7,
            "I0" => 98, "I1" => 88, "I2" => 78, "I3" => 68, "I4" => 58,
            "I5" => 48, "I6" => 38, "I7" => 28, "I8" => 18, "I9" => 8,
            "J0" => 99, "J1" => 89, "J2" => 79, "J3" => 69, "J4" => 59,
            "J5" => 49, "J6" => 39, "J7" => 29, "J8" => 19, "J9" => 9,
            _ => 100,
        };

        if input == 100 {
            println!("Invalid field, try again.")
        }
    }
    input
}

fn match_move(first: &mut types::Player, second: &mut types::Player, idx: usize) {

    match second.own_board[idx] {
        types::SubField::Water => {
            println!("Miss - try again.");
            first.op_board[idx] = types::SubField::WaterHit;
        }
        types::SubField::Ship => {
            println!("Hit!");
            first.op_board[idx] = types::SubField::Hit;
            second.own_board[idx] = types::SubField::Hit;
            second.capacity -= 1;
        },
        types::SubField::Hit => { println!("Already hit."); }
        types::SubField::WaterHit => { println!("Miss - try again."); }
    }
}

/// Lets the players perform their moves.
fn make_move(mut first: &mut types::Player, mut second: &mut types::Player) {
    println!("Enter coordinates, {}:", first.name);
    let input = get_input();
    match_move(&mut first, &mut second, input);
}

/// Returns whether all the player's ships got destroyed.
pub fn game_over(player: &types::Player) -> bool {
    player.capacity <= 0
}

pub fn start_match() {

    /* Creates the initial (empty) boards (10 x 10) for player1 and player2. */
    let mut player1 = types::Player {
        own_board: vec![types::SubField::Water; 100],
        op_board: vec![types::SubField::Water; 100],
        capacity: 0,
        name: "Player1".to_string(),
    };

    let mut player2 = types::Player {
        own_board: vec![types::SubField::Water; 100],
        op_board: vec![types::SubField::Water; 100],
        capacity: 0,
        name: "Player2".to_string(),
    };

    place_ships(&mut player1, &mut player2);

    loop {
        print_boards(&player1.own_board, &player1.op_board);
        make_move(&mut player1, &mut player2);
        if game_over(&player2) {
            println!("G A M E   O V E R");
            println!("Congratulations, Player1");
            break;
        }

        print_boards(&player2.own_board, &player2.op_board);
        make_move(&mut player2, &mut player1);
        if game_over(&player1) {
            println!("G A M E   O V E R");
            println!("Congratulations, Player2");
            break;
        }
    }
}
<commit_msg>minor changes<commit_after>use term_painter::ToStyle;
use term_painter::Color::*;
use std::io;

mod types;

// Game logic (board, initialization, valid move, set, play or finished)

/// Visualization of the boards.
fn print_boards(board1: &Vec<types::SubField>, board2: &Vec<types::SubField>) {
    println!("{}{}", "------------------ O W N   B O A R D ------------------",
        "------------------------- O P P O N E N T ----------------");

    let mut cnt = 9;

    for i in 0..10 {
        println!("");
        print!("{}  ", cnt);
        for j in 0..10 {
            if board1[j + i * 10] == types::SubField::Hit {
                print!(" {} ", Red.paint(board1[j + i * 10]));
            } else {
                print!(" {} ", board1[j + i * 10]);
            }
        }
        print!("        ");
        print!("{}  ", cnt);
        for j in 0..10 {
            if board2[j + i * 10] == types::SubField::Hit {
                print!(" {} ", Green.paint(board2[j + i * 10]));
            } else {
                print!(" {} ", board2[j + i * 10]);
            }
        }
        println!("");
        cnt -= 1;
    }
    println!("");
    println!("{}{}", "     A    B    C    D    E    F    G    H    I    J",
        "               A    B    C    D    E    F    G    H    I    J");
    println!("");
    println!("{}{}", "----------------------------------------------------",
        "--------------------------------------------------------------");
}

/// Reads a string from the terminal / user.
fn read_string() -> String {
    let mut buffer = String::new();
    io::stdin()
        .read_line(&mut buffer)
        .expect("something went horribly wrong...");

    /* Discard trailing newline */
    let new_len = buffer.trim_right().len();
    buffer.truncate(new_len);

    buffer
}

/// The actual placement of the ships.
fn place(player: &mut types::Player, ship: &types::ShipType) -> u8 {

    /* Vector to collect the indices for the possible ship position. */
    let mut indices = Vec::new();

    /* 100 --> invalid value */
    let mut input = 100;
    while input == 100 {
        input = get_input();
        match player.own_board[input] {
            types::SubField::Water => { indices.push(input); },
            _ => {
                println!("Invalid input, again please.");
                input = 100;
            },
        }
    }

    println!("{}{}", "Enter 'h' for a horizontal (upwards) orientation of the ship,",
        "'v' for a vertical (rightwards) one.");
    /* x --> invalid value */
    let mut orientation = "x".to_string();
    while orientation == "x" {
        orientation = read_string();
        if orientation != "h" && orientation != "v" {
            println!("Invalid input, again please.");
            orientation = "x".to_string();
        }
    }

    if orientation == "v" {
        for _ in 0..ship.size - 1 {
            /* Check to prevent out of bounds errors. */
            if input >= 10 {
                input -= 10;
            }
            /* input < 10 --> no field above */
            if player.own_board[input] != types::SubField::Water || input < 10 {
                    println!("Invalid position for this ship.");
                    println!("Please choose another coordinate.");
                    return 1
            }
            indices.push(input);
        }
    } else {
        for _ in 0..ship.size - 1 {
            /* Check to prevent out of bounds errors. */
            if input < 99 {
                input += 1;
            }
            /* unit position == 9 --> no field to the right */
            if player.own_board[input] != types::SubField::Water || (input % 10) == 9 {
                    println!("Invalid position for this ship.");
                    println!("Please choose another coordinate.");
                    return 1
            }
            indices.push(input);
        }
    }

    /* Places the ships on the board. */
    for i in indices.iter() {
        player.own_board[*i] = types::SubField::Ship;
    }

    0
}

/// Handles the initial ship placement for each player.
fn place_ships(mut player1: &mut types::Player, mut player2: &mut types::Player) {

    /* A vector of all the ships each player needs to place.
       The default version: #  Class of ship Size
                            1   Carrier       5
                            2   Battleship    4
                            3   Cruiser       3
                            4   Submarine     3
                            5   Destroyer     2
    */
    let s1 = types::ShipType{ name: "Carrier".to_string(), size: 5, amount: 1 };
    let s2 = types::ShipType{ name: "Battleship".to_string(), size: 4, amount: 2 };
    let s3 = types::ShipType{ name: "Cruiser".to_string(), size: 3, amount: 3 };
    let s4 = types::ShipType{ name: "Submarine".to_string(), size: 3, amount: 4 };
    let s5 = types::ShipType{ name: "Destroyer".to_string(), size: 2, amount: 5 };
    let ships = vec![s1, s2, s3, s4, s5];

    print_boards(&player1.own_board, &player1.op_board);

    /* Error code, should be replaced by proper error handling later. */
    let mut err;

    /* Asks player1 to place the ships. */
    for i in ships.iter() {
        for _ in 0..i.amount {
            err = 1;
            while err == 1 {
                println!("{}, please enter the first coordinate for your {:?} ({} fields).",
                    player1.name, i.name, i.size);
                err = place(&mut player1, i);
            }
            player1.capacity += i.size;
            print_boards(&player1.own_board, &player1.op_board);
        }
    }

    /* Asks player2 to place the ships. */
    for i in ships.iter() {
        for _ in 0..i.amount {
            err = 1;
            while err == 1 {
                println!("{}, please enter the first coordinate for your {:?} ({} fields).",
                    player2.name, i.name, i.size);
                err = place(&mut player2, i);
            }
            player2.capacity += i.size;
            print_boards(&player2.own_board, &player2.op_board);
        }
    }
}

/// Reads the coordinates of a field from the user
/// and returns the corresponding index.
fn get_input() -> usize {

    /* 100 --> invalid */
    let mut input = 100;

    while input == 100 {
        input = match read_string().as_ref() {
            "A0" => 90, "A1" => 80,"A2" => 70, "A3" => 60, "A4" => 50,
            "A5" => 40, "A6" => 30, "A7" => 20, "A8" => 10, "A9" => 0,
            "B0" => 91, "B1" => 81, "B2" => 71, "B3" => 61, "B4" => 51,
            "B5" => 41, "B6" => 31, "B7" => 21, "B8" => 11, "B9" => 1,
            "C0" => 92, "C1" => 82, "C2" => 72, "C3" => 62, "C4" => 52,
            "C5" => 42, "C6" => 32, "C7" => 22, "C8" => 12, "C9" => 2,
            "D0" => 93, "D1" => 83, "D2" => 73, "D3" => 63, "D4" => 53,
            "D5" => 43, "D6" => 33, "D7" => 23, "D8" => 13, "D9" => 3,
            "E0" => 94, "E1" => 84, "E2" => 74, "E3" => 64, "E4" => 54,
            "E5" => 44, "E6" => 34, "E7" => 24, "E8" => 14, "E9" => 4,
            "F0" => 95, "F1" => 85, "F2" => 75, "F3" => 65, "F4" => 55,
            "F5" => 45, "F6" => 35, "F7" => 25, "F8" => 15, "F9" => 5,
            "G0" => 96, "G1" => 86, "G2" => 76, "G3" => 66, "G4" => 56,
            "G5" => 46, "G6" => 36, "G7" => 26, "G8" => 16, "G9" => 6,
            "H0" => 97, "H1" => 87, "H2" => 77, "H3" => 67, "H4" => 57,
            "H5" => 47, "H6" => 37, "H7" => 27, "H8" => 17, "H9" => 7,
            "I0" => 98, "I1" => 88, "I2" => 78, "I3" => 68, "I4" => 58,
            "I5" => 48, "I6" => 38, "I7" => 28, "I8" => 18, "I9" => 8,
            "J0" => 99, "J1" => 89, "J2" => 79, "J3" => 69, "J4" => 59,
            "J5" => 49, "J6" => 39, "J7" => 29, "J8" => 19, "J9" => 9,
            _ => 100,
        };

        if input == 100 {
            println!("Invalid field, try again.")
        }
    }
    input
}

/// Determines the type of the SubField that got hit
/// by the current move and sets it accordingly.
fn match_move(first: &mut types::Player, second: &mut types::Player, idx: usize) {

    match second.own_board[idx] {
        types::SubField::Water => {
            println!("Miss - try again.");
            first.op_board[idx] = types::SubField::WaterHit;
        }
        types::SubField::Ship => {
            println!("Hit!");
            first.op_board[idx] = types::SubField::Hit;
            second.own_board[idx] = types::SubField::Hit;
            second.capacity -= 1;
        },
        types::SubField::Hit => { println!("Already hit."); }
        types::SubField::WaterHit => { println!("Miss - try again."); }
    }
}

/// Lets the players perform their moves.
fn make_move(mut first: &mut types::Player, mut second: &mut types::Player) {
    println!("Enter coordinates, {}:", first.name);
    let input = get_input();
    match_move(&mut first, &mut second, input);
}

/// Returns whether all the player's ships got destroyed.
pub fn game_over(player: &types::Player) -> bool {
    player.capacity <= 0
}

/// Initializes the players and the boards and provides the
/// game loop which lets the players perform their moves alternately.
pub fn start_match() {

    /* Creates the initial (empty) boards (10 x 10) for player1. */
    let mut player1 = types::Player {
        own_board: vec![types::SubField::Water; 100],
        op_board: vec![types::SubField::Water; 100],
        capacity: 0,
        name: "Player1".to_string(),
    };
    /* Creates the initial (empty) boards (10 x 10) for player2. */
    let mut player2 = types::Player {
        own_board: vec![types::SubField::Water; 100],
        op_board: vec![types::SubField::Water; 100],
        capacity: 0,
        name: "Player2".to_string(),
    };

    /* Initializes the boards with the player's ships. */
    place_ships(&mut player1, &mut player2);

    loop {
        print_boards(&player1.own_board, &player1.op_board);
        make_move(&mut player1, &mut player2);
        if game_over(&player2) {
            println!("G A M E   O V E R");
            println!("Congratulations, Player1");
            break;
        }

        print_boards(&player2.own_board, &player2.op_board);
        make_move(&mut player2, &mut player1);
        if game_over(&player1) {
            println!("G A M E   O V E R");
            println!("Congratulations, Player2");
            break;
        }
    }
}
<|endoftext|>


<commit_before>//! This module defines a series of convenience modifiers for changing
//! Responses.
//!
//! Modifiers can be used to edit `Response`s through the owning method `set`
//! or the mutating `set_mut`, both of which are defined in the `Set` trait.
//!
//! For Iron, the `Modifier` interface offers extensible and ergonomic response
//! creation while avoiding the introduction of many highly specific `Response`
//! constructors.
//!
//! The simplest case of a modifier is probably the one used to change the
//! return status code:
//!
//! ```
//! # use std::convert::Into;
//! # use iron::prelude::*;
//! # use iron::StatusCode;
//! let r = Response::with(StatusCode::NOT_FOUND);
//! assert_eq!(404 as u16, r.status.unwrap().as_u16());
//! ```
//!
//! You can also pass in a tuple of modifiers, they will all be applied. Here's
//! an example of a modifier 2-tuple that will change the status code and the
//! body message:
//!
//! ```
//! # use iron::prelude::*;
//! # use iron::StatusCode;
//! Response::with((StatusCode::IM_A_TEAPOT, "I am a tea pot!"));
//! ```
//!
//! There is also a `Redirect` modifier:
//!
//! ```
//! # use iron::prelude::*;
//! # use iron::StatusCode;
//! # use iron::modifiers;
//! # use iron::Url;
//! let url = Url::parse("http://doc.rust-lang.org").unwrap();
//! Response::with((StatusCode::FOUND, modifiers::Redirect(url)));
//! ```
//!
//! The modifiers are applied depending on their type. Currently the easiest
//! way to see how different types are used as modifiers, take a look at [the
//! source code](https://github.com/iron/iron/blob/master/src/modifiers.rs).
//!
//! For more information about the modifier system, see
//! [rust-modifier](https://github.com/reem/rust-modifier).

use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};

use modifier::Modifier;

use mime::{self, Mime};

use {headers, StatusCode, Request, Response, Set, Url};

use mime_guess::guess_mime_type_opt;
use response::{WriteBody, BodyReader};

impl Modifier<Response> for Mime {
    #[inline]
    fn modify(self, res: &mut Response) {
        res.headers.insert(headers::CONTENT_TYPE, self.as_ref().parse().unwrap()); // TODO: error handling?
    }
}

impl Modifier<Response> for Box<WriteBody> {
    #[inline]
    fn modify(self, res: &mut Response) {
        res.body = Some(self);
    }
}

impl <R: io::Read + Send + 'static> Modifier<Response> for BodyReader<R> {
    #[inline]
    fn modify(self, res: &mut Response) {
        res.body = Some(Box::new(self));
    }
}

impl Modifier<Response> for String {
    #[inline]
    fn modify(self, res: &mut Response) {
        self.into_bytes().modify(res);
    }
}

impl Modifier<Response> for Vec<u8> {
    #[inline]
    fn modify(self, res: &mut Response) {
        res.headers.insert(headers::CONTENT_LENGTH, (self.len() as u64).to_string().parse().unwrap()); // TODO: error handling?
        res.body = Some(Box::new(self));
    }
}

impl<'a> Modifier<Response> for &'a str {
    #[inline]
    fn modify(self, res: &mut Response) {
        self.to_owned().modify(res);
    }
}

impl<'a> Modifier<Response> for &'a [u8] {
    #[inline]
    fn modify(self, res: &mut Response) {
        self.to_vec().modify(res);
    }
}

impl Modifier<Response> for File {
    fn modify(self, res: &mut Response) {
        // Set the content type based on the file extension if a path is available.
        if let Ok(metadata) = self.metadata() {
            res.headers.insert(headers::CONTENT_LENGTH, (&metadata.len().to_string()).parse().unwrap()); // TODO: error handling?
        }

        res.body = Some(Box::new(self));
    }
}

impl<'a> Modifier<Response> for &'a Path {
    /// Set the body to the contents of the File at this path.
    ///
    /// ## Panics
    ///
    /// Panics if there is no file at the passed-in Path.
    fn modify(self, res: &mut Response) {
        File::open(self)
            .unwrap_or_else(|_| panic!("No such file: {}", self.display()))
            .modify(res);

        let mime = mime_for_path(self);
        res.set_mut(mime);
    }
}

impl Modifier<Response> for PathBuf {
    /// Set the body to the contents of the File at this path.
    ///
    /// ## Panics
    ///
    /// Panics if there is no file at the passed-in Path.
    #[inline]
    fn modify(self, res: &mut Response) {
        self.as_path().modify(res);
    }
}

impl Modifier<Response> for StatusCode {
    fn modify(self, res: &mut Response) {
        res.status = Some(self);
    }
}

/// A modifier for changing headers on requests and responses.
#[derive(Clone)]
pub struct Header<H>(pub H, pub headers::HeaderValue);

impl<H> Modifier<Response> for Header<H>
where H: headers::IntoHeaderName {
    fn modify(self, res: &mut Response) {
        res.headers.insert(self.0, self.1);
    }
}

impl<H> Modifier<Request> for Header<H>
where H: headers::IntoHeaderName {
    fn modify(self, res: &mut Request) {
        res.headers.insert(self.0, self.1);
    }
}

/// A modifier for creating redirect responses.
pub struct Redirect(pub Url);

impl Modifier<Response> for Redirect {
    fn modify(self, res: &mut Response) {
        let Redirect(url) = self;
        res.headers.insert(headers::LOCATION, url.to_string().parse().unwrap()); // TODO: error handling?
    }
}

/// A modifier for creating redirect responses.
pub struct RedirectRaw(pub String);

impl Modifier<Response> for RedirectRaw {
    fn modify(self, res: &mut Response) {
        let RedirectRaw(path) = self;
        res.headers.insert(headers::LOCATION, path.parse().unwrap()); // TODO: error handling?
    }
}

fn mime_for_path(path: &Path) -> Mime {
    guess_mime_type_opt(path)
        .unwrap_or(mime::TEXT_PLAIN)
}

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

    #[test]
    fn test_mime_for_path() {
        assert_eq!(mime_for_path(Path::new("foo.txt")),
                   mime::TEXT_PLAIN);
        assert_eq!(mime_for_path(Path::new("foo.jpg")),
                   mime::IMAGE_JPEG);
        assert_eq!(mime_for_path(Path::new("foo.zip")),
                   "application/zip".parse::<Mime>().unwrap());
        assert_eq!(mime_for_path(Path::new("foo")),
                   mime::TEXT_PLAIN);
    }
}
<commit_msg>(cleanup) modifiers: cleanup HeaderValue parsing<commit_after>//! This module defines a series of convenience modifiers for changing
//! Responses.
//!
//! Modifiers can be used to edit `Response`s through the owning method `set`
//! or the mutating `set_mut`, both of which are defined in the `Set` trait.
//!
//! For Iron, the `Modifier` interface offers extensible and ergonomic response
//! creation while avoiding the introduction of many highly specific `Response`
//! constructors.
//!
//! The simplest case of a modifier is probably the one used to change the
//! return status code:
//!
//! ```
//! # use std::convert::Into;
//! # use iron::prelude::*;
//! # use iron::StatusCode;
//! let r = Response::with(StatusCode::NOT_FOUND);
//! assert_eq!(404 as u16, r.status.unwrap().as_u16());
//! ```
//!
//! You can also pass in a tuple of modifiers, they will all be applied. Here's
//! an example of a modifier 2-tuple that will change the status code and the
//! body message:
//!
//! ```
//! # use iron::prelude::*;
//! # use iron::StatusCode;
//! Response::with((StatusCode::IM_A_TEAPOT, "I am a tea pot!"));
//! ```
//!
//! There is also a `Redirect` modifier:
//!
//! ```
//! # use iron::prelude::*;
//! # use iron::StatusCode;
//! # use iron::modifiers;
//! # use iron::Url;
//! let url = Url::parse("http://doc.rust-lang.org").unwrap();
//! Response::with((StatusCode::FOUND, modifiers::Redirect(url)));
//! ```
//!
//! The modifiers are applied depending on their type. Currently the easiest
//! way to see how different types are used as modifiers, take a look at [the
//! source code](https://github.com/iron/iron/blob/master/src/modifiers.rs).
//!
//! For more information about the modifier system, see
//! [rust-modifier](https://github.com/reem/rust-modifier).

use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};

use modifier::Modifier;

use mime::{self, Mime};

use {headers, StatusCode, Request, Response, Set, Url};

use mime_guess::guess_mime_type_opt;
use response::{WriteBody, BodyReader};

impl Modifier<Response> for Mime {
    #[inline]
    fn modify(self, res: &mut Response) {
        // Mime should always be parsable to a valid HeaderValue, so unwrap should be safe here.
        res.headers.insert(headers::CONTENT_TYPE, self.as_ref().parse().unwrap());
    }
}

impl Modifier<Response> for Box<WriteBody> {
    #[inline]
    fn modify(self, res: &mut Response) {
        res.body = Some(self);
    }
}

impl <R: io::Read + Send + 'static> Modifier<Response> for BodyReader<R> {
    #[inline]
    fn modify(self, res: &mut Response) {
        res.body = Some(Box::new(self));
    }
}

impl Modifier<Response> for String {
    #[inline]
    fn modify(self, res: &mut Response) {
        self.into_bytes().modify(res);
    }
}

impl Modifier<Response> for Vec<u8> {
    #[inline]
    fn modify(self, res: &mut Response) {
        res.headers.insert(headers::CONTENT_LENGTH, (self.len() as u64).into());
        res.body = Some(Box::new(self));
    }
}

impl<'a> Modifier<Response> for &'a str {
    #[inline]
    fn modify(self, res: &mut Response) {
        self.to_owned().modify(res);
    }
}

impl<'a> Modifier<Response> for &'a [u8] {
    #[inline]
    fn modify(self, res: &mut Response) {
        self.to_vec().modify(res);
    }
}

impl Modifier<Response> for File {
    fn modify(self, res: &mut Response) {
        // Set the content type based on the file extension if a path is available.
        if let Ok(metadata) = self.metadata() {
            res.headers.insert(headers::CONTENT_LENGTH, metadata.len().into());
        }

        res.body = Some(Box::new(self));
    }
}

impl<'a> Modifier<Response> for &'a Path {
    /// Set the body to the contents of the File at this path.
    ///
    /// ## Panics
    ///
    /// Panics if there is no file at the passed-in Path.
    fn modify(self, res: &mut Response) {
        File::open(self)
            .unwrap_or_else(|_| panic!("No such file: {}", self.display()))
            .modify(res);

        let mime = mime_for_path(self);
        res.set_mut(mime);
    }
}

impl Modifier<Response> for PathBuf {
    /// Set the body to the contents of the File at this path.
    ///
    /// ## Panics
    ///
    /// Panics if there is no file at the passed-in Path.
    #[inline]
    fn modify(self, res: &mut Response) {
        self.as_path().modify(res);
    }
}

impl Modifier<Response> for StatusCode {
    fn modify(self, res: &mut Response) {
        res.status = Some(self);
    }
}

/// A modifier for changing headers on requests and responses.
#[derive(Clone)]
pub struct Header<H>(pub H, pub headers::HeaderValue);

impl<H> Modifier<Response> for Header<H>
where H: headers::IntoHeaderName {
    fn modify(self, res: &mut Response) {
        res.headers.insert(self.0, self.1);
    }
}

impl<H> Modifier<Request> for Header<H>
where H: headers::IntoHeaderName {
    fn modify(self, res: &mut Request) {
        res.headers.insert(self.0, self.1);
    }
}

/// A modifier for creating redirect responses.
pub struct Redirect(pub Url);

impl Modifier<Response> for Redirect {
    fn modify(self, res: &mut Response) {
        let Redirect(url) = self;
        res.headers.insert(headers::LOCATION, url.to_string().parse().unwrap()); // TODO: error handling?
    }
}

/// A modifier for creating redirect responses.
pub struct RedirectRaw(pub String);

impl Modifier<Response> for RedirectRaw {
    fn modify(self, res: &mut Response) {
        let RedirectRaw(path) = self;
        res.headers.insert(headers::LOCATION, path.parse().unwrap()); // TODO: error handling?
    }
}

fn mime_for_path(path: &Path) -> Mime {
    guess_mime_type_opt(path)
        .unwrap_or(mime::TEXT_PLAIN)
}

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

    #[test]
    fn test_mime_for_path() {
        assert_eq!(mime_for_path(Path::new("foo.txt")),
                   mime::TEXT_PLAIN);
        assert_eq!(mime_for_path(Path::new("foo.jpg")),
                   mime::IMAGE_JPEG);
        assert_eq!(mime_for_path(Path::new("foo.zip")),
                   "application/zip".parse::<Mime>().unwrap());
        assert_eq!(mime_for_path(Path::new("foo")),
                   mime::TEXT_PLAIN);
    }
}
<|endoftext|>


<commit_before>use crate::{
    prelude::{verify::TxErr, *},
    serializer::*,
};
use std::{
    collections::BTreeSet,
    io::{self, Cursor, Error},
    mem,
    sync::Arc,
};

#[derive(Clone, Debug, PartialEq)]
pub struct Request {
    /// A 32-bit unsigned integer max value is reserved for deserialization errors that occur during request processing.
    /// When a request is received with a reserved id, an IO error is returned regardless if the request is valid.
    pub id: u32,
    pub body: RequestBody,
}

impl Request {
    pub fn serialize(&self, buf: &mut Vec<u8>) {
        buf.push_u32(self.id);
        self.body.serialize(buf);
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let id = cursor.take_u32()?;
        let body = RequestBody::deserialize(cursor)?;
        Ok(Self { id, body })
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Response {
    /// A 32-bit unsigned integer max value represents an IO error during processing the request. Note that sending a
    /// request with a max value will be treated as an IO error regardless if the request is valid.
    pub id: u32,
    pub body: ResponseBody,
}

impl Response {
    pub fn serialize(&self, buf: &mut Vec<u8>) {
        buf.push_u32(self.id);
        self.body.serialize(buf);
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let id = cursor.take_u32()?;
        let body = ResponseBody::deserialize(cursor)?;
        Ok(Self { id, body })
    }
}

#[repr(u8)]
pub enum BodyType {
    // Returned to clients when an error occurred processing a request
    Error = 0x01,

    // Operations that can update the connection or blockchain state
    Broadcast = 0x10,
    SetBlockFilter = 0x11,

    // Getters
    GetProperties = 0x20,
    GetBlock = 0x21,
    GetBlockHeader = 0x22,
    GetAddressInfo = 0x23,
}

#[derive(Clone, Debug, PartialEq)]
pub enum RequestBody {
    Broadcast(TxVariant),
    SetBlockFilter(BlockFilter),
    GetProperties,
    GetBlock(u64),       // height
    GetBlockHeader(u64), // height
    GetAddressInfo(ScriptHash),
}

impl RequestBody {
    pub fn serialize(&self, buf: &mut Vec<u8>) {
        match self {
            Self::Broadcast(tx) => {
                buf.reserve_exact(4096);
                buf.push(BodyType::Broadcast as u8);
                tx.serialize(buf);
            }
            Self::SetBlockFilter(filter) => {
                buf.push(BodyType::SetBlockFilter as u8);
                match filter {
                    BlockFilter::None => {
                        // Addr len
                        buf.push(0);
                    }
                    BlockFilter::Addr(addrs) => {
                        buf.reserve_exact(1 + addrs.len() * mem::size_of::<ScriptHash>());
                        buf.push(addrs.len() as u8);
                        for addr in addrs {
                            buf.push_digest(&addr.0);
                        }
                    }
                }
            }
            Self::GetProperties => buf.push(BodyType::GetProperties as u8),
            Self::GetBlock(height) => {
                buf.reserve_exact(9);
                buf.push(BodyType::GetBlock as u8);
                buf.push_u64(*height);
            }
            Self::GetBlockHeader(height) => {
                buf.reserve_exact(9);
                buf.push(BodyType::GetBlockHeader as u8);
                buf.push_u64(*height);
            }
            Self::GetAddressInfo(addr) => {
                buf.reserve_exact(33);
                buf.push(BodyType::GetAddressInfo as u8);
                buf.push_digest(&addr.0);
            }
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == BodyType::Broadcast as u8 => {
                let tx = TxVariant::deserialize(cursor)
                    .ok_or_else(|| Error::new(io::ErrorKind::InvalidData, "failed to decode tx"))?;
                Ok(Self::Broadcast(tx))
            }
            t if t == BodyType::SetBlockFilter as u8 => {
                let addr_len = usize::from(cursor.take_u8()?);
                if addr_len == 0 {
                    Ok(Self::SetBlockFilter(BlockFilter::None))
                } else {
                    let mut addrs = BTreeSet::new();
                    for _ in 0..addr_len {
                        addrs.insert(ScriptHash(cursor.take_digest()?));
                    }
                    Ok(Self::SetBlockFilter(BlockFilter::Addr(addrs)))
                }
            }
            t if t == BodyType::GetProperties as u8 => Ok(Self::GetProperties),
            t if t == BodyType::GetBlock as u8 => {
                let height = cursor.take_u64()?;
                Ok(Self::GetBlock(height))
            }
            t if t == BodyType::GetBlockHeader as u8 => {
                let height = cursor.take_u64()?;
                Ok(Self::GetBlockHeader(height))
            }
            t if t == BodyType::GetAddressInfo as u8 => {
                let addr = ScriptHash(cursor.take_digest()?);
                Ok(Self::GetAddressInfo(addr))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg request",
            )),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum ResponseBody {
    Error(ErrorKind),
    Broadcast,
    SetBlockFilter,
    GetProperties(Properties),
    GetBlock(FilteredBlock),
    GetBlockHeader {
        header: BlockHeader,
        signer: SigPair,
    },
    GetAddressInfo(AddressInfo),
}

impl ResponseBody {
    pub fn is_err(&self) -> bool {
        match self {
            Self::Error(..) => true,
            _ => false,
        }
    }

    pub fn serialize(&self, buf: &mut Vec<u8>) {
        match self {
            Self::Error(err) => {
                buf.reserve_exact(2048);
                buf.push(BodyType::Error as u8);
                err.serialize(buf);
            }
            Self::Broadcast => buf.push(BodyType::Broadcast as u8),
            Self::SetBlockFilter => buf.push(BodyType::SetBlockFilter as u8),
            Self::GetProperties(props) => {
                buf.reserve_exact(4096 + mem::size_of::<Properties>());
                buf.push(BodyType::GetProperties as u8);
                buf.push_u64(props.height);
                {
                    let mut tx_buf = Vec::with_capacity(4096);
                    props.owner.serialize(&mut tx_buf);
                    buf.extend_from_slice(&tx_buf);
                }
                buf.push_asset(props.network_fee);
                buf.push_asset(props.token_supply);
            }
            Self::GetBlock(block) => {
                buf.reserve_exact(1_048_576);
                buf.push(BodyType::GetBlock as u8);
                match block {
                    FilteredBlock::Header((header, signer)) => {
                        buf.push(0);
                        header.serialize(buf);
                        buf.push_sig_pair(signer);
                    }
                    FilteredBlock::Block(block) => {
                        buf.push(1);
                        block.serialize(buf);
                    }
                }
            }
            Self::GetBlockHeader { header, signer } => {
                buf.reserve_exact(256);
                buf.push(BodyType::GetBlockHeader as u8);
                header.serialize(buf);
                buf.push_sig_pair(signer);
            }
            Self::GetAddressInfo(info) => {
                buf.reserve_exact(1 + (mem::size_of::<Asset>() * 3));
                buf.push(BodyType::GetAddressInfo as u8);
                buf.push_asset(info.net_fee);
                buf.push_asset(info.addr_fee);
                buf.push_asset(info.balance);
            }
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == BodyType::Error as u8 => {
                let err = ErrorKind::deserialize(cursor)?;
                Ok(Self::Error(err))
            }
            t if t == BodyType::Broadcast as u8 => Ok(Self::Broadcast),
            t if t == BodyType::SetBlockFilter as u8 => Ok(Self::SetBlockFilter),
            t if t == BodyType::GetProperties as u8 => {
                let height = cursor.take_u64()?;
                let owner = {
                    let tx = TxVariant::deserialize(cursor).ok_or_else(|| {
                        Error::new(io::ErrorKind::InvalidData, "failed to deserialize owner tx")
                    })?;
                    match tx {
                        TxVariant::V0(ref var) => match var {
                            TxVariantV0::OwnerTx(_) => Box::new(tx),
                            _ => {
                                return Err(Error::new(
                                    io::ErrorKind::InvalidData,
                                    "expected owner tx",
                                ))
                            }
                        },
                    }
                };
                let network_fee = cursor.take_asset()?;
                let token_supply = cursor.take_asset()?;
                Ok(Self::GetProperties(Properties {
                    height,
                    owner,
                    network_fee,
                    token_supply,
                }))
            }
            t if t == BodyType::GetBlock as u8 => {
                let filtered_type = cursor.take_u8()?;
                match filtered_type {
                    0 => {
                        let header = BlockHeader::deserialize(cursor)
                            .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                        let signer = cursor.take_sig_pair()?;
                        Ok(Self::GetBlock(FilteredBlock::Header((header, signer))))
                    }
                    1 => {
                        let block = Block::deserialize(cursor)
                            .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                        Ok(Self::GetBlock(FilteredBlock::Block(Arc::new(block))))
                    }
                    _ => Err(Error::new(
                        io::ErrorKind::InvalidData,
                        "invalid GetBlock response",
                    )),
                }
            }
            t if t == BodyType::GetBlockHeader as u8 => {
                let header = BlockHeader::deserialize(cursor)
                    .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                let signer = cursor.take_sig_pair()?;
                Ok(Self::GetBlockHeader { header, signer })
            }
            t if t == BodyType::GetAddressInfo as u8 => {
                let net_fee = cursor.take_asset()?;
                let addr_fee = cursor.take_asset()?;
                let balance = cursor.take_asset()?;
                Ok(Self::GetAddressInfo(AddressInfo {
                    net_fee,
                    addr_fee,
                    balance,
                }))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg response",
            )),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ErrorKind {
    Io,
    BytesRemaining,
    InvalidRequest,
    InvalidHeight,
    TxValidation(TxErr),
}

impl ErrorKind {
    fn serialize(self, buf: &mut Vec<u8>) {
        match self {
            Self::Io => buf.push(0x00),
            Self::BytesRemaining => buf.push(0x01),
            Self::InvalidRequest => buf.push(0x02),
            Self::InvalidHeight => buf.push(0x03),
            Self::TxValidation(err) => {
                buf.push(0x04);
                err.serialize(buf);
            }
        }
    }

    fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        Ok(match tag {
            0x00 => Self::Io,
            0x01 => Self::BytesRemaining,
            0x02 => Self::InvalidRequest,
            0x03 => Self::InvalidHeight,
            0x04 => Self::TxValidation(TxErr::deserialize(cursor)?),
            _ => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    "failed to deserialize ErrorKind",
                ))
            }
        })
    }
}
<commit_msg>Add Subscribe and Unsubscribe net APIs<commit_after>use crate::{
    prelude::{verify::TxErr, *},
    serializer::*,
};
use std::{
    collections::BTreeSet,
    io::{self, Cursor, Error},
    mem,
    sync::Arc,
};

#[derive(Clone, Debug, PartialEq)]
pub struct Request {
    /// A 32-bit unsigned integer max value is reserved for deserialization errors that occur during request processing.
    /// When a request is received with a reserved id, an IO error is returned regardless if the request is valid.
    pub id: u32,
    pub body: RequestBody,
}

impl Request {
    pub fn serialize(&self, buf: &mut Vec<u8>) {
        buf.push_u32(self.id);
        self.body.serialize(buf);
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let id = cursor.take_u32()?;
        let body = RequestBody::deserialize(cursor)?;
        Ok(Self { id, body })
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Response {
    /// A 32-bit unsigned integer max value represents an IO error during processing the request. Note that sending a
    /// request with a max value will be treated as an IO error regardless if the request is valid.
    pub id: u32,
    pub body: ResponseBody,
}

impl Response {
    pub fn serialize(&self, buf: &mut Vec<u8>) {
        buf.push_u32(self.id);
        self.body.serialize(buf);
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let id = cursor.take_u32()?;
        let body = ResponseBody::deserialize(cursor)?;
        Ok(Self { id, body })
    }
}

#[repr(u8)]
pub enum BodyType {
    // Returned to clients when an error occurred processing a request
    Error = 0x01,

    // Operations that can update the connection or blockchain state
    Broadcast = 0x10,
    SetBlockFilter = 0x11,
    /// Subscribe to receive block updates. Any block filters applied may be ignored.
    Subscribe = 0x12,
    /// Unsubscribe from receiving block updates.
    Unsubscribe = 0x13,

    // Getters
    GetProperties = 0x20,
    GetBlock = 0x21,
    GetBlockHeader = 0x22,
    GetAddressInfo = 0x23,
}

#[derive(Clone, Debug, PartialEq)]
pub enum RequestBody {
    Broadcast(TxVariant),
    SetBlockFilter(BlockFilter),
    Subscribe,
    Unsubscribe,
    GetProperties,
    GetBlock(u64),       // height
    GetBlockHeader(u64), // height
    GetAddressInfo(ScriptHash),
}

impl RequestBody {
    pub fn serialize(&self, buf: &mut Vec<u8>) {
        match self {
            Self::Broadcast(tx) => {
                buf.reserve_exact(4096);
                buf.push(BodyType::Broadcast as u8);
                tx.serialize(buf);
            }
            Self::SetBlockFilter(filter) => {
                buf.push(BodyType::SetBlockFilter as u8);
                match filter {
                    BlockFilter::None => {
                        // Addr len
                        buf.push(0);
                    }
                    BlockFilter::Addr(addrs) => {
                        buf.reserve_exact(1 + addrs.len() * mem::size_of::<ScriptHash>());
                        buf.push(addrs.len() as u8);
                        for addr in addrs {
                            buf.push_digest(&addr.0);
                        }
                    }
                }
            }
            Self::Subscribe => buf.push(BodyType::Subscribe as u8),
            Self::Unsubscribe => buf.push(BodyType::Unsubscribe as u8),
            Self::GetProperties => buf.push(BodyType::GetProperties as u8),
            Self::GetBlock(height) => {
                buf.reserve_exact(9);
                buf.push(BodyType::GetBlock as u8);
                buf.push_u64(*height);
            }
            Self::GetBlockHeader(height) => {
                buf.reserve_exact(9);
                buf.push(BodyType::GetBlockHeader as u8);
                buf.push_u64(*height);
            }
            Self::GetAddressInfo(addr) => {
                buf.reserve_exact(33);
                buf.push(BodyType::GetAddressInfo as u8);
                buf.push_digest(&addr.0);
            }
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == BodyType::Broadcast as u8 => {
                let tx = TxVariant::deserialize(cursor)
                    .ok_or_else(|| Error::new(io::ErrorKind::InvalidData, "failed to decode tx"))?;
                Ok(Self::Broadcast(tx))
            }
            t if t == BodyType::SetBlockFilter as u8 => {
                let addr_len = usize::from(cursor.take_u8()?);
                if addr_len == 0 {
                    Ok(Self::SetBlockFilter(BlockFilter::None))
                } else {
                    let mut addrs = BTreeSet::new();
                    for _ in 0..addr_len {
                        addrs.insert(ScriptHash(cursor.take_digest()?));
                    }
                    Ok(Self::SetBlockFilter(BlockFilter::Addr(addrs)))
                }
            }
            t if t == BodyType::Subscribe as u8 => Ok(Self::Subscribe),
            t if t == BodyType::Unsubscribe as u8 => Ok(Self::Unsubscribe),
            t if t == BodyType::GetProperties as u8 => Ok(Self::GetProperties),
            t if t == BodyType::GetBlock as u8 => {
                let height = cursor.take_u64()?;
                Ok(Self::GetBlock(height))
            }
            t if t == BodyType::GetBlockHeader as u8 => {
                let height = cursor.take_u64()?;
                Ok(Self::GetBlockHeader(height))
            }
            t if t == BodyType::GetAddressInfo as u8 => {
                let addr = ScriptHash(cursor.take_digest()?);
                Ok(Self::GetAddressInfo(addr))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg request",
            )),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum ResponseBody {
    Error(ErrorKind),
    Broadcast,
    SetBlockFilter,
    Subscribe,
    Unsubscribe,
    GetProperties(Properties),
    GetBlock(FilteredBlock),
    GetBlockHeader {
        header: BlockHeader,
        signer: SigPair,
    },
    GetAddressInfo(AddressInfo),
}

impl ResponseBody {
    pub fn is_err(&self) -> bool {
        match self {
            Self::Error(..) => true,
            _ => false,
        }
    }

    pub fn serialize(&self, buf: &mut Vec<u8>) {
        match self {
            Self::Error(err) => {
                buf.reserve_exact(2048);
                buf.push(BodyType::Error as u8);
                err.serialize(buf);
            }
            Self::Broadcast => buf.push(BodyType::Broadcast as u8),
            Self::SetBlockFilter => buf.push(BodyType::SetBlockFilter as u8),
            Self::Subscribe => buf.push(BodyType::Subscribe as u8),
            Self::Unsubscribe => buf.push(BodyType::Unsubscribe as u8),
            Self::GetProperties(props) => {
                buf.reserve_exact(4096 + mem::size_of::<Properties>());
                buf.push(BodyType::GetProperties as u8);
                buf.push_u64(props.height);
                {
                    let mut tx_buf = Vec::with_capacity(4096);
                    props.owner.serialize(&mut tx_buf);
                    buf.extend_from_slice(&tx_buf);
                }
                buf.push_asset(props.network_fee);
                buf.push_asset(props.token_supply);
            }
            Self::GetBlock(block) => {
                buf.reserve_exact(1_048_576);
                buf.push(BodyType::GetBlock as u8);
                match block {
                    FilteredBlock::Header((header, signer)) => {
                        buf.push(0);
                        header.serialize(buf);
                        buf.push_sig_pair(signer);
                    }
                    FilteredBlock::Block(block) => {
                        buf.push(1);
                        block.serialize(buf);
                    }
                }
            }
            Self::GetBlockHeader { header, signer } => {
                buf.reserve_exact(256);
                buf.push(BodyType::GetBlockHeader as u8);
                header.serialize(buf);
                buf.push_sig_pair(signer);
            }
            Self::GetAddressInfo(info) => {
                buf.reserve_exact(1 + (mem::size_of::<Asset>() * 3));
                buf.push(BodyType::GetAddressInfo as u8);
                buf.push_asset(info.net_fee);
                buf.push_asset(info.addr_fee);
                buf.push_asset(info.balance);
            }
        }
    }

    pub fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        match tag {
            t if t == BodyType::Error as u8 => {
                let err = ErrorKind::deserialize(cursor)?;
                Ok(Self::Error(err))
            }
            t if t == BodyType::Broadcast as u8 => Ok(Self::Broadcast),
            t if t == BodyType::SetBlockFilter as u8 => Ok(Self::SetBlockFilter),
            t if t == BodyType::Subscribe as u8 => Ok(Self::Subscribe),
            t if t == BodyType::Unsubscribe as u8 => Ok(Self::Unsubscribe),
            t if t == BodyType::GetProperties as u8 => {
                let height = cursor.take_u64()?;
                let owner = {
                    let tx = TxVariant::deserialize(cursor).ok_or_else(|| {
                        Error::new(io::ErrorKind::InvalidData, "failed to deserialize owner tx")
                    })?;
                    match tx {
                        TxVariant::V0(ref var) => match var {
                            TxVariantV0::OwnerTx(_) => Box::new(tx),
                            _ => {
                                return Err(Error::new(
                                    io::ErrorKind::InvalidData,
                                    "expected owner tx",
                                ))
                            }
                        },
                    }
                };
                let network_fee = cursor.take_asset()?;
                let token_supply = cursor.take_asset()?;
                Ok(Self::GetProperties(Properties {
                    height,
                    owner,
                    network_fee,
                    token_supply,
                }))
            }
            t if t == BodyType::GetBlock as u8 => {
                let filtered_type = cursor.take_u8()?;
                match filtered_type {
                    0 => {
                        let header = BlockHeader::deserialize(cursor)
                            .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                        let signer = cursor.take_sig_pair()?;
                        Ok(Self::GetBlock(FilteredBlock::Header((header, signer))))
                    }
                    1 => {
                        let block = Block::deserialize(cursor)
                            .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                        Ok(Self::GetBlock(FilteredBlock::Block(Arc::new(block))))
                    }
                    _ => Err(Error::new(
                        io::ErrorKind::InvalidData,
                        "invalid GetBlock response",
                    )),
                }
            }
            t if t == BodyType::GetBlockHeader as u8 => {
                let header = BlockHeader::deserialize(cursor)
                    .ok_or_else(|| Error::from(io::ErrorKind::UnexpectedEof))?;
                let signer = cursor.take_sig_pair()?;
                Ok(Self::GetBlockHeader { header, signer })
            }
            t if t == BodyType::GetAddressInfo as u8 => {
                let net_fee = cursor.take_asset()?;
                let addr_fee = cursor.take_asset()?;
                let balance = cursor.take_asset()?;
                Ok(Self::GetAddressInfo(AddressInfo {
                    net_fee,
                    addr_fee,
                    balance,
                }))
            }
            _ => Err(Error::new(
                io::ErrorKind::InvalidData,
                "invalid msg response",
            )),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ErrorKind {
    Io,
    BytesRemaining,
    InvalidRequest,
    InvalidHeight,
    TxValidation(TxErr),
}

impl ErrorKind {
    fn serialize(self, buf: &mut Vec<u8>) {
        match self {
            Self::Io => buf.push(0x00),
            Self::BytesRemaining => buf.push(0x01),
            Self::InvalidRequest => buf.push(0x02),
            Self::InvalidHeight => buf.push(0x03),
            Self::TxValidation(err) => {
                buf.push(0x04);
                err.serialize(buf);
            }
        }
    }

    fn deserialize(cursor: &mut Cursor<&[u8]>) -> io::Result<Self> {
        let tag = cursor.take_u8()?;
        Ok(match tag {
            0x00 => Self::Io,
            0x01 => Self::BytesRemaining,
            0x02 => Self::InvalidRequest,
            0x03 => Self::InvalidHeight,
            0x04 => Self::TxValidation(TxErr::deserialize(cursor)?),
            _ => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    "failed to deserialize ErrorKind",
                ))
            }
        })
    }
}
<|endoftext|>


<commit_before>//! Resolves and rewrites links in markdown documentation for hovers/completion windows.

use std::iter::once;

use itertools::Itertools;
use pulldown_cmark::{CowStr, Event, Options, Parser, Tag};
use pulldown_cmark_to_cmark::cmark;
use url::Url;

use hir::{Adt, AsName, AttrDef, Crate, Hygiene, ItemInNs, ModPath, ModuleDef};
use ra_hir_def::db::DefDatabase;
use ra_ide_db::{defs::Definition, RootDatabase};
use ra_syntax::ast::Path;
use ra_tt::{Ident, Leaf, Literal, TokenTree};

/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
    let doc = Parser::new_with_broken_link_callback(
        markdown,
        Options::empty(),
        Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))),
    );

    let doc = map_links(doc, |target, title: &str| {
        // This check is imperfect, there's some overlap between valid intra-doc links
        // and valid URLs so we choose to be too eager to try to resolve what might be
        // a URL.
        if target.contains("://") {
            (target.to_string(), title.to_string())
        } else {
            // Two posibilities:
            // * path-based links: `../../module/struct.MyStruct.html`
            // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
            let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| {
                try_resolve_path(db, definition, &target).map(|target| (target, title.to_string()))
            });

            match resolved {
                Some((target, title)) => (target, title),
                None => (target.to_string(), title.to_string()),
            }
        }
    });
    let mut out = String::new();
    cmark(doc, &mut out, None).ok();
    out
}

// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
fn map_links<'e>(
    events: impl Iterator<Item = Event<'e>>,
    callback: impl Fn(&str, &str) -> (String, String),
) -> impl Iterator<Item = Event<'e>> {
    let mut in_link = false;
    let mut link_target: Option<CowStr> = None;

    events.map(move |evt| match evt {
        Event::Start(Tag::Link(_link_type, ref target, _)) => {
            in_link = true;
            link_target = Some(target.clone());
            evt
        }
        Event::End(Tag::Link(link_type, _target, _)) => {
            in_link = false;
            Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
        }
        Event::Text(s) if in_link => {
            let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
            link_target = Some(CowStr::Boxed(link_target_s.into()));
            Event::Text(CowStr::Boxed(link_name.into()))
        }
        Event::Code(s) if in_link => {
            let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
            link_target = Some(CowStr::Boxed(link_target_s.into()));
            Event::Code(CowStr::Boxed(link_name.into()))
        }
        _ => evt,
    })
}

#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
enum Namespace {
    Types,
    Values,
    Macros,
}

static TYPES: ([&str; 7], [&str; 0]) =
    (["type", "struct", "enum", "mod", "trait", "union", "module"], []);
static VALUES: ([&str; 8], [&str; 1]) =
    (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]);
static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]);

impl Namespace {
    /// Extract the specified namespace from an intra-doc-link if one exists.
    ///
    /// # Examples
    ///
    /// * `struct MyStruct` -> `Namespace::Types`
    /// * `panic!` -> `Namespace::Macros`
    /// * `fn@from_intra_spec` -> `Namespace::Values`
    fn from_intra_spec(s: &str) -> Option<Self> {
        [
            (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())),
            (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())),
            (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())),
        ]
        .iter()
        .filter(|(_ns, (prefixes, suffixes))| {
            prefixes
                .clone()
                .map(|prefix| {
                    s.starts_with(*prefix)
                        && s.chars()
                            .nth(prefix.len() + 1)
                            .map(|c| c == '@' || c == ' ')
                            .unwrap_or(false)
                })
                .any(|cond| cond)
                || suffixes
                    .clone()
                    .map(|suffix| {
                        s.starts_with(*suffix)
                            && s.chars()
                                .nth(suffix.len() + 1)
                                .map(|c| c == '@' || c == ' ')
                                .unwrap_or(false)
                    })
                    .any(|cond| cond)
        })
        .map(|(ns, (_, _))| *ns)
        .next()
    }
}

// Strip prefixes, suffixes, and inline code marks from the given string.
fn strip_prefixes_suffixes(mut s: &str) -> &str {
    s = s.trim_matches('`');

    [
        (TYPES.0.iter(), TYPES.1.iter()),
        (VALUES.0.iter(), VALUES.1.iter()),
        (MACROS.0.iter(), MACROS.1.iter()),
    ]
    .iter()
    .for_each(|(prefixes, suffixes)| {
        prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix));
        suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix));
    });
    s.trim_start_matches("@").trim()
}

/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
///
/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
fn try_resolve_intra(
    db: &RootDatabase,
    definition: &Definition,
    link_text: &str,
    link_target: &str,
) -> Option<(String, String)> {
    eprintln!("resolving intra");

    // Set link_target for implied shortlinks
    let link_target =
        if link_target.is_empty() { link_text.trim_matches('`') } else { link_target };

    // Namespace disambiguation
    let namespace = Namespace::from_intra_spec(link_target);

    // Strip prefixes/suffixes
    let link_target = strip_prefixes_suffixes(link_target);

    // Parse link as a module path
    let path = Path::parse(link_target).ok()?;
    let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();

    // Resolve it relative to symbol's location (according to the RFC this should consider small scopes
    let resolver = definition.resolver(db)?;

    let resolved = resolver.resolve_module_path_in_items(db, &modpath);
    let (defid, namespace) = match namespace {
        // FIXME: .or(resolved.macros)
        None => resolved
            .types
            .map(|t| (t.0, Namespace::Types))
            .or(resolved.values.map(|t| (t.0, Namespace::Values)))?,
        Some(ns @ Namespace::Types) => (resolved.types?.0, ns),
        Some(ns @ Namespace::Values) => (resolved.values?.0, ns),
        // FIXME:
        Some(Namespace::Macros) => None?,
    };

    // Get the filepath of the final symbol
    let def: ModuleDef = defid.into();
    let module = def.module(db)?;
    let krate = module.krate();
    let ns = match namespace {
        Namespace::Types => ItemInNs::Types(defid),
        Namespace::Values => ItemInNs::Values(defid),
        // FIXME:
        Namespace::Macros => None?,
    };
    let import_map = db.import_map(krate.into());
    let path = import_map.path_of(ns)?;

    Some((
        get_doc_url(db, &krate)?
            .join(&format!("{}/", krate.display_name(db)?))
            .ok()?
            .join(&path.segments.iter().map(|name| name.to_string()).join("/"))
            .ok()?
            .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
            .ok()?
            .into_string(),
        strip_prefixes_suffixes(link_text).to_string(),
    ))
}

/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option<String> {
    eprintln!("resolving path");

    if !link.contains("#") && !link.contains(".html") {
        return None;
    }
    let ns = if let Definition::ModuleDef(moddef) = definition {
        ItemInNs::Types(moddef.clone().into())
    } else {
        return None;
    };
    let module = definition.module(db)?;
    let krate = module.krate();
    let import_map = db.import_map(krate.into());
    let base = once(format!("{}", krate.display_name(db)?))
        .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name)))
        .join("/");

    get_doc_url(db, &krate)
        .and_then(|url| url.join(&base).ok())
        .and_then(|url| {
            get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()
        })
        .and_then(|url| url.join(link).ok())
        .map(|url| url.into_string())
}

/// Try to get the root URL of the documentation of a crate.
fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
    // Look for #![doc(html_root_url = "...")]
    let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into());
    let doc_attr_q = attrs.by_key("doc");

    let doc_url = if doc_attr_q.exists() {
        doc_attr_q.tt_values().map(|tt| {
            let name = tt.token_trees.iter()
                .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url"))
                .skip(2)
                .next();

            match name {
                Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text),
                _ => None
            }
        }).flat_map(|t| t).next().map(|s| s.to_string())
    } else {
        // Fallback to docs.rs
        // FIXME: Specify an exact version here (from Cargo.lock)
        Some(format!("https://docs.rs/{}/*", krate.display_name(db)?))
    };

    doc_url
        .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/")
        .and_then(|s| Url::parse(&s).ok())
}

/// Get the filename and extension generated for a symbol by rustdoc.
///
/// Example: `struct.Shard.html`
fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option<String> {
    Some(match definition {
        Definition::ModuleDef(def) => match def {
            ModuleDef::Adt(adt) => match adt {
                Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
                Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
                Adt::Union(u) => format!("union.{}.html", u.name(db)),
            },
            ModuleDef::Module(_) => "index.html".to_string(),
            ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
            ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
            ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
            ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
            ModuleDef::EnumVariant(ev) => {
                format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
            }
            ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?),
            ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
        },
        Definition::Macro(m) => format!("macro.{}.html", m.name(db)?),
        _ => None?,
    })
}
<commit_msg>remove debug eprintln!<commit_after>//! Resolves and rewrites links in markdown documentation for hovers/completion windows.

use std::iter::once;

use itertools::Itertools;
use pulldown_cmark::{CowStr, Event, Options, Parser, Tag};
use pulldown_cmark_to_cmark::cmark;
use url::Url;

use hir::{Adt, AsName, AttrDef, Crate, Hygiene, ItemInNs, ModPath, ModuleDef};
use ra_hir_def::db::DefDatabase;
use ra_ide_db::{defs::Definition, RootDatabase};
use ra_syntax::ast::Path;
use ra_tt::{Ident, Leaf, Literal, TokenTree};

/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
    let doc = Parser::new_with_broken_link_callback(
        markdown,
        Options::empty(),
        Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))),
    );

    let doc = map_links(doc, |target, title: &str| {
        // This check is imperfect, there's some overlap between valid intra-doc links
        // and valid URLs so we choose to be too eager to try to resolve what might be
        // a URL.
        if target.contains("://") {
            (target.to_string(), title.to_string())
        } else {
            // Two posibilities:
            // * path-based links: `../../module/struct.MyStruct.html`
            // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
            let resolved = try_resolve_intra(db, definition, title, &target).or_else(|| {
                try_resolve_path(db, definition, &target).map(|target| (target, title.to_string()))
            });

            match resolved {
                Some((target, title)) => (target, title),
                None => (target.to_string(), title.to_string()),
            }
        }
    });
    let mut out = String::new();
    cmark(doc, &mut out, None).ok();
    out
}

// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
fn map_links<'e>(
    events: impl Iterator<Item = Event<'e>>,
    callback: impl Fn(&str, &str) -> (String, String),
) -> impl Iterator<Item = Event<'e>> {
    let mut in_link = false;
    let mut link_target: Option<CowStr> = None;

    events.map(move |evt| match evt {
        Event::Start(Tag::Link(_link_type, ref target, _)) => {
            in_link = true;
            link_target = Some(target.clone());
            evt
        }
        Event::End(Tag::Link(link_type, _target, _)) => {
            in_link = false;
            Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
        }
        Event::Text(s) if in_link => {
            let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
            link_target = Some(CowStr::Boxed(link_target_s.into()));
            Event::Text(CowStr::Boxed(link_name.into()))
        }
        Event::Code(s) if in_link => {
            let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
            link_target = Some(CowStr::Boxed(link_target_s.into()));
            Event::Code(CowStr::Boxed(link_name.into()))
        }
        _ => evt,
    })
}

#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
enum Namespace {
    Types,
    Values,
    Macros,
}

static TYPES: ([&str; 7], [&str; 0]) =
    (["type", "struct", "enum", "mod", "trait", "union", "module"], []);
static VALUES: ([&str; 8], [&str; 1]) =
    (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]);
static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]);

impl Namespace {
    /// Extract the specified namespace from an intra-doc-link if one exists.
    ///
    /// # Examples
    ///
    /// * `struct MyStruct` -> `Namespace::Types`
    /// * `panic!` -> `Namespace::Macros`
    /// * `fn@from_intra_spec` -> `Namespace::Values`
    fn from_intra_spec(s: &str) -> Option<Self> {
        [
            (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())),
            (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())),
            (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())),
        ]
        .iter()
        .filter(|(_ns, (prefixes, suffixes))| {
            prefixes
                .clone()
                .map(|prefix| {
                    s.starts_with(*prefix)
                        && s.chars()
                            .nth(prefix.len() + 1)
                            .map(|c| c == '@' || c == ' ')
                            .unwrap_or(false)
                })
                .any(|cond| cond)
                || suffixes
                    .clone()
                    .map(|suffix| {
                        s.starts_with(*suffix)
                            && s.chars()
                                .nth(suffix.len() + 1)
                                .map(|c| c == '@' || c == ' ')
                                .unwrap_or(false)
                    })
                    .any(|cond| cond)
        })
        .map(|(ns, (_, _))| *ns)
        .next()
    }
}

// Strip prefixes, suffixes, and inline code marks from the given string.
fn strip_prefixes_suffixes(mut s: &str) -> &str {
    s = s.trim_matches('`');

    [
        (TYPES.0.iter(), TYPES.1.iter()),
        (VALUES.0.iter(), VALUES.1.iter()),
        (MACROS.0.iter(), MACROS.1.iter()),
    ]
    .iter()
    .for_each(|(prefixes, suffixes)| {
        prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix));
        suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix));
    });
    s.trim_start_matches("@").trim()
}

/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
///
/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
fn try_resolve_intra(
    db: &RootDatabase,
    definition: &Definition,
    link_text: &str,
    link_target: &str,
) -> Option<(String, String)> {
    // Set link_target for implied shortlinks
    let link_target =
        if link_target.is_empty() { link_text.trim_matches('`') } else { link_target };

    // Namespace disambiguation
    let namespace = Namespace::from_intra_spec(link_target);

    // Strip prefixes/suffixes
    let link_target = strip_prefixes_suffixes(link_target);

    // Parse link as a module path
    let path = Path::parse(link_target).ok()?;
    let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();

    // Resolve it relative to symbol's location (according to the RFC this should consider small scopes)
    let resolver = definition.resolver(db)?;

    let resolved = resolver.resolve_module_path_in_items(db, &modpath);
    let (defid, namespace) = match namespace {
        // FIXME: .or(resolved.macros)
        None => resolved
            .types
            .map(|t| (t.0, Namespace::Types))
            .or(resolved.values.map(|t| (t.0, Namespace::Values)))?,
        Some(ns @ Namespace::Types) => (resolved.types?.0, ns),
        Some(ns @ Namespace::Values) => (resolved.values?.0, ns),
        // FIXME:
        Some(Namespace::Macros) => None?,
    };

    // Get the filepath of the final symbol
    let def: ModuleDef = defid.into();
    let module = def.module(db)?;
    let krate = module.krate();
    let ns = match namespace {
        Namespace::Types => ItemInNs::Types(defid),
        Namespace::Values => ItemInNs::Values(defid),
        // FIXME:
        Namespace::Macros => None?,
    };
    let import_map = db.import_map(krate.into());
    let path = import_map.path_of(ns)?;

    Some((
        get_doc_url(db, &krate)?
            .join(&format!("{}/", krate.display_name(db)?))
            .ok()?
            .join(&path.segments.iter().map(|name| name.to_string()).join("/"))
            .ok()?
            .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?)
            .ok()?
            .into_string(),
        strip_prefixes_suffixes(link_text).to_string(),
    ))
}

/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option<String> {
    if !link.contains("#") && !link.contains(".html") {
        return None;
    }
    let ns = if let Definition::ModuleDef(moddef) = definition {
        ItemInNs::Types(moddef.clone().into())
    } else {
        return None;
    };
    let module = definition.module(db)?;
    let krate = module.krate();
    let import_map = db.import_map(krate.into());
    let base = once(format!("{}", krate.display_name(db)?))
        .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name)))
        .join("/");

    get_doc_url(db, &krate)
        .and_then(|url| url.join(&base).ok())
        .and_then(|url| {
            get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()
        })
        .and_then(|url| url.join(link).ok())
        .map(|url| url.into_string())
}

/// Try to get the root URL of the documentation of a crate.
fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
    // Look for #![doc(html_root_url = "...")]
    let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into());
    let doc_attr_q = attrs.by_key("doc");

    let doc_url = if doc_attr_q.exists() {
        doc_attr_q.tt_values().map(|tt| {
            let name = tt.token_trees.iter()
                .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url"))
                .skip(2)
                .next();

            match name {
                Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text),
                _ => None
            }
        }).flat_map(|t| t).next().map(|s| s.to_string())
    } else {
        // Fallback to docs.rs
        // FIXME: Specify an exact version here (from Cargo.lock)
        Some(format!("https://docs.rs/{}/*", krate.display_name(db)?))
    };

    doc_url
        .map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/")
        .and_then(|s| Url::parse(&s).ok())
}

/// Get the filename and extension generated for a symbol by rustdoc.
///
/// Example: `struct.Shard.html`
fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option<String> {
    Some(match definition {
        Definition::ModuleDef(def) => match def {
            ModuleDef::Adt(adt) => match adt {
                Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
                Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
                Adt::Union(u) => format!("union.{}.html", u.name(db)),
            },
            ModuleDef::Module(_) => "index.html".to_string(),
            ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
            ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
            ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
            ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
            ModuleDef::EnumVariant(ev) => {
                format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
            }
            ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?),
            ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
        },
        Definition::Macro(m) => format!("macro.{}.html", m.name(db)?),
        _ => None?,
    })
}
<|endoftext|>