<commit_before>extern crate portaudio;

use portaudio::{pa, stream, hostapi, device};

fn main()
{
    println!("version: {} \"{}\"", pa::version(), pa::version_text());
    println!("init: {}", pa::initialize());

    print_info();
    doit();

    println!("term: {}", pa::terminate());
}

fn print_info()
{
    match hostapi::get_count()
    {
        Ok(api_count) => {
            for i in range(0, api_count)
            {
                let name = match hostapi::get_info(i)
                {
                    None => "???".to_string(),
                    Some(ha) => ha.name,
                };
                println!("api {}: {}", i, name);
            }
        },
        _ => {},
    }

    match device::get_count()
    {
        Ok(device_count) => {
            for i in range(0, device_count)
            {
                let name = match device::get_info(i)
                {
                    None => "???".to_string(),
                    Some(d) => d.name,
                };
                println!("dev {}: {}", i, name);
            }
        },
        _ => {},
    }
}

fn doit()
{
    callback_demo();
    write_demo();
    mixed_demo();
}

fn callback_demo()
{
    let callback = |_input: &[f32], output: &mut [f32], _time: stream::StreamTimeInfo, _flags: stream::StreamCallbackFlags| -> stream::StreamCallbackResult
    {
        static mut lp: f32 = 0.0;
        static mut rp: f32 = 0.0;

        let mut left_phase = unsafe { lp };
        let mut right_phase = unsafe { rp };

        for i in range(0, output.len() / 2)
        {
            output[i*2] = left_phase;
            output[i*2+1] = right_phase;

            left_phase += 0.01;
            if left_phase >= 1.0 { left_phase -= 2.0; }

            right_phase += 0.03;
            if right_phase >= 1.0 { right_phase -= 2.0; }
        }

        unsafe { lp = left_phase; }
        unsafe { rp = right_phase; }

        stream::Continue
    };

    let mut stream = match stream::Stream::open_default(0, 2, 44100f64, stream::FRAMES_PER_BUFFER_UNSPECIFIED, Some(callback))
    {
        Err(v) => { println!("Err({})", v); return },
        Ok(stream) => stream,
    };
    println!("finished_callback: {}", stream.set_finished_callback(|| println!("Finished callback called")));
    println!("start: {}", stream.start());
    std::io::timer::sleep(std::time::duration::Duration::seconds(1));
    println!("stop: {}", stream.stop());

    println!("finished_callback: {}", stream.unset_finished_callback());
    println!("start: {}", stream.start());
    std::io::timer::sleep(std::time::duration::Duration::seconds(1));
    println!("stop: {}", stream.stop());
}

fn write_demo()
{
    let stream = match stream::Stream::open_default(0, 2, 44100f64, stream::FRAMES_PER_BUFFER_UNSPECIFIED, None)
    {
        Err(v) => { println!("Err({})", v); return },
        Ok(stream) => stream,
    };

    println!("start: {}", stream.start());
    println!("write: {}", stream.write(get_buffer(44100*3).as_slice()));
    println!("stop: {}", stream.stop());
}

fn get_buffer(len: uint) -> Vec<f32>
{
    let mut left = 0.0f32;
    let mut right = 0.0f32;
    let mut result = Vec::with_capacity(len);
    for _ in range(0, len / 2)
    {
        result.push(left);
        result.push(right);
        left += 0.03;
        right += 0.01;
        if left >= 1.0 { left -= 2.0; }
        if right >= 1.0 { right -= 2.0; }
    }
    result
}

fn mixed_demo()
{
    let in_idx = match device::get_default_input_index()
    {
        Ok(i) => i,
        Err(_) => return,
    };
    let out_idx = match device::get_default_output_index()
    {
        Ok(o) => o,
        Err(_) => return,
    };
    let in_lat = match device::get_info(in_idx)
    {
        None => return,
        Some(d) => d.default_low_input_latency,
    };
    let out_lat = match device::get_info(out_idx)
    {
        None => return,
        Some(d) => d.default_low_output_latency,
    };
    let input: stream::StreamParameters<f32> = stream::StreamParameters { device: in_idx, channel_count: 2, suggested_latency: in_lat };
    let output: stream::StreamParameters<i8> = stream::StreamParameters { device: out_idx, channel_count: 2, suggested_latency: out_lat };

    let supported = stream::is_format_supported(input, output, 44100f64);
    println!("support? {}", supported);
    if supported.is_err() { return }

    let stream = match stream::Stream::open(input, output, 44100f64, stream::FRAMES_PER_BUFFER_UNSPECIFIED, stream::StreamFlags::empty(), None)
    {
        Ok(s) => s,
        Err(o) => { println!("stream: Err({})", o); return },
    };

    let buffer = get_buffer(2*44100).into_iter().map(|v| (v * 127.0) as i8).collect::<Vec<i8>>();
    println!("start: {}", stream.start());
    println!("write: {}", stream.write(buffer.as_slice()));
    println!("stop: {}", stream.stop());
}
<commit_msg>Fix demo<commit_after>extern crate portaudio;

use portaudio::{pa, stream, hostapi, device};

fn main()
{
    println!("version: {} \"{}\"", pa::version(), pa::version_text());
    println!("init: {}", pa::initialize());

    print_info();
    doit();

    println!("term: {}", pa::terminate());
}

fn print_info()
{
    match hostapi::get_count()
    {
        Ok(api_count) => {
            for i in range(0, api_count)
            {
                let name = match hostapi::get_info(i)
                {
                    None => "???".to_string(),
                    Some(ha) => ha.name,
                };
                println!("api {}: {}", i, name);
            }
        },
        _ => {},
    }

    match device::get_count()
    {
        Ok(device_count) => {
            for i in range(0, device_count)
            {
                let name = match device::get_info(i)
                {
                    None => "???".to_string(),
                    Some(d) => d.name,
                };
                println!("dev {}: {}", i, name);
            }
        },
        _ => {},
    }
}

fn doit()
{
    callback_demo();
    write_demo();
    mixed_demo();
}

fn callback_demo()
{
    let callback = |_input: &[f32], output: &mut [f32], _time: stream::StreamTimeInfo, _flags: stream::StreamCallbackFlags| -> stream::StreamCallbackResult
    {
        static mut lp: f32 = 0.0;
        static mut rp: f32 = 0.0;

        let mut left_phase = unsafe { lp };
        let mut right_phase = unsafe { rp };

        for i in range(0, output.len() / 2)
        {
            output[i*2] = left_phase;
            output[i*2+1] = right_phase;

            left_phase += 0.01;
            if left_phase >= 1.0 { left_phase -= 2.0; }

            right_phase += 0.03;
            if right_phase >= 1.0 { right_phase -= 2.0; }
        }

        unsafe { lp = left_phase; }
        unsafe { rp = right_phase; }

        stream::StreamCallbackResult::Continue
    };

    let mut stream = match stream::Stream::open_default(0, 2, 44100f64, stream::FRAMES_PER_BUFFER_UNSPECIFIED, Some(callback))
    {
        Err(v) => { println!("Err({})", v); return },
        Ok(stream) => stream,
    };
    println!("finished_callback: {}", stream.set_finished_callback(|| println!("Finished callback called")));
    println!("start: {}", stream.start());
    std::io::timer::sleep(std::time::duration::Duration::seconds(1));
    println!("stop: {}", stream.stop());

    println!("finished_callback: {}", stream.unset_finished_callback());
    println!("start: {}", stream.start());
    std::io::timer::sleep(std::time::duration::Duration::seconds(1));
    println!("stop: {}", stream.stop());
}

fn write_demo()
{
    let stream = match stream::Stream::open_default(0, 2, 44100f64, stream::FRAMES_PER_BUFFER_UNSPECIFIED, None)
    {
        Err(v) => { println!("Err({})", v); return },
        Ok(stream) => stream,
    };

    println!("start: {}", stream.start());
    println!("write: {}", stream.write(get_buffer(44100*3).as_slice()));
    println!("stop: {}", stream.stop());
}

fn get_buffer(len: uint) -> Vec<f32>
{
    let mut left = 0.0f32;
    let mut right = 0.0f32;
    let mut result = Vec::with_capacity(len);
    for _ in range(0, len / 2)
    {
        result.push(left);
        result.push(right);
        left += 0.03;
        right += 0.01;
        if left >= 1.0 { left -= 2.0; }
        if right >= 1.0 { right -= 2.0; }
    }
    result
}

fn mixed_demo()
{
    let in_idx = match device::get_default_input_index()
    {
        Ok(i) => i,
        Err(_) => return,
    };
    let out_idx = match device::get_default_output_index()
    {
        Ok(o) => o,
        Err(_) => return,
    };
    let in_lat = match device::get_info(in_idx)
    {
        None => return,
        Some(d) => d.default_low_input_latency,
    };
    let out_lat = match device::get_info(out_idx)
    {
        None => return,
        Some(d) => d.default_low_output_latency,
    };
    let input: stream::StreamParameters<f32> = stream::StreamParameters { device: in_idx, channel_count: 2, suggested_latency: in_lat };
    let output: stream::StreamParameters<i8> = stream::StreamParameters { device: out_idx, channel_count: 2, suggested_latency: out_lat };

    let supported = stream::is_format_supported(input, output, 44100f64);
    println!("support? {}", supported);
    if supported.is_err() { return }

    let stream = match stream::Stream::open(input, output, 44100f64, stream::FRAMES_PER_BUFFER_UNSPECIFIED, stream::StreamFlags::empty(), None)
    {
        Ok(s) => s,
        Err(o) => { println!("stream: Err({})", o); return },
    };

    let buffer = get_buffer(2*44100).into_iter().map(|v| (v * 127.0) as i8).collect::<Vec<i8>>();
    println!("start: {}", stream.start());
    println!("write: {}", stream.write(buffer.as_slice()));
    println!("stop: {}", stream.stop());
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Once;

use libc;

macro_rules! debug {
    ($($args:expr),*) => ({
        if false {
        //if true {
            println!($($args),*);
        }
    });
}

macro_rules! some {
    ($e:expr) => {{
        match $e {
            Some(v) => v,
            None => {
                debug!("NONE: {:?}", stringify!($e));
                return None;
            }
        }
    }};
}

pub fn get_num_cpus() -> usize {
    match cgroups_num_cpus() {
        Some(n) => n,
        None => logical_cpus(),
    }
}

fn logical_cpus() -> usize {
    let mut set: libc::cpu_set_t = unsafe { mem::zeroed() };
    if unsafe { libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) } == 0 {
        let mut count: u32 = 0;
        for i in 0..libc::CPU_SETSIZE as usize {
            if unsafe { libc::CPU_ISSET(i, &set) } {
                count += 1
            }
        }
        count as usize
    } else {
        let cpus = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) };
        if cpus < 1 {
            1
        } else {
            cpus as usize
        }
    }
}

pub fn get_num_physical_cpus() -> usize {
    let file = match File::open("/proc/cpuinfo") {
        Ok(val) => val,
        Err(_) => return get_num_cpus(),
    };
    let reader = BufReader::new(file);
    let mut map = HashMap::new();
    let mut physid: u32 = 0;
    let mut cores: usize = 0;
    let mut chgcount = 0;
    for line in reader.lines().filter_map(|result| result.ok()) {
        let mut it = line.split(':');
        let (key, value) = match (it.next(), it.next()) {
            (Some(key), Some(value)) => (key.trim(), value.trim()),
            _ => continue,
        };
        if key == "physical id" {
            match value.parse() {
                Ok(val) => physid = val,
                Err(_) => break,
            };
            chgcount += 1;
        }
        if key == "cpu cores" {
            match value.parse() {
                Ok(val) => cores = val,
                Err(_) => break,
            };
            chgcount += 1;
        }
        if chgcount == 2 {
            map.insert(physid, cores);
            chgcount = 0;
        }
    }
    let count = map.into_iter().fold(0, |acc, (_, cores)| acc + cores);

    if count == 0 {
        get_num_cpus()
    } else {
        count
    }
}

/// Cached CPUs calculated from cgroups.
///
/// If 0, check logical cpus.
// Allow deprecation warnings, we want to work on older rustc
#[allow(warnings)]
static CGROUPS_CPUS: AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;

fn cgroups_num_cpus() -> Option<usize> {
    #[allow(warnings)]
    static ONCE: Once = ::std::sync::ONCE_INIT;

    ONCE.call_once(init_cgroups);

    let cpus = CGROUPS_CPUS.load(Ordering::Acquire);

    if cpus > 0 {
        Some(cpus)
    } else {
        None
    }
}

fn init_cgroups() {
    // Should only be called once
    debug_assert!(CGROUPS_CPUS.load(Ordering::SeqCst) == 0);

    if let Some(quota) = load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") {
        if quota == 0 {
            return;
        }

        let logical = logical_cpus();
        let count = ::std::cmp::min(quota, logical);

        CGROUPS_CPUS.store(count, Ordering::SeqCst);
    }
}

fn load_cgroups<P1, P2>(cgroup_proc: P1, mountinfo_proc: P2) -> Option<usize>
where
    P1: AsRef<Path>,
    P2: AsRef<Path>,
{
    let subsys = some!(Subsys::load_cpu(cgroup_proc));
    let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc));
    let cgroup = some!(Cgroup::translate(mntinfo, subsys));
    cgroup.cpu_quota()
}

struct Cgroup {
    base: PathBuf,
}

struct MountInfo {
    root: String,
    mount_point: String,
}

struct Subsys {
    base: String,
}

impl Cgroup {
    fn new(dir: PathBuf) -> Cgroup {
        Cgroup { base: dir }
    }

    fn translate(mntinfo: MountInfo, subsys: Subsys) -> Option<Cgroup> {
        // Translate the subsystem directory via the host paths.
        debug!(
            "subsys = {:?}; root = {:?}; mount_point = {:?}",
            subsys.base, mntinfo.root, mntinfo.mount_point
        );

        let rel_from_root = some!(Path::new(&subsys.base).strip_prefix(&mntinfo.root).ok());

        debug!("rel_from_root: {:?}", rel_from_root);

        // join(mp.MountPoint, relPath)
        let mut path = PathBuf::from(mntinfo.mount_point);
        path.push(rel_from_root);
        Some(Cgroup::new(path))
    }

    fn cpu_quota(&self) -> Option<usize> {
        let quota_us = some!(self.quota_us());
        let period_us = some!(self.period_us());

        // protect against dividing by zero
        if period_us == 0 {
            return None;
        }

        // Ceil the division, since we want to be able to saturate
        // the available CPUs, and flooring would leave a CPU un-utilized.

        Some((quota_us as f64 / period_us as f64).ceil() as usize)
    }

    fn quota_us(&self) -> Option<usize> {
        self.param("cpu.cfs_quota_us")
    }

    fn period_us(&self) -> Option<usize> {
        self.param("cpu.cfs_period_us")
    }

    fn param(&self, param: &str) -> Option<usize> {
        let mut file = some!(File::open(self.base.join(param)).ok());

        let mut buf = String::new();
        some!(file.read_to_string(&mut buf).ok());

        buf.trim().parse().ok()
    }
}

impl MountInfo {
    fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<MountInfo> {
        let file = some!(File::open(proc_path).ok());
        let file = BufReader::new(file);

        file.lines()
            .filter_map(|result| result.ok())
            .filter_map(MountInfo::parse_line)
            .next()
    }

    fn parse_line(line: String) -> Option<MountInfo> {
        let mut fields = line.split(' ');

        // 7 5 0:6 </> /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct
        let mnt_root = some!(fields.nth(3));
        // 7 5 0:6 / </sys/fs/cgroup/cpu,cpuacct> rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct
        let mnt_point = some!(fields.next());

        // Ignore all fields until the separator(-).
        // Note: there could be zero or more optional fields before hyphen.
        // See: https://man7.org/linux/man-pages/man5/proc.5.html
        // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 <-> cgroup cgroup rw,cpu,cpuacct
        // Note: we cannot use `?` here because we need to support Rust 1.13.
        match fields.find(|&s| s == "-") {
            Some(_) => {}
            None => return None,
        };

        // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - <cgroup> cgroup rw,cpu,cpuacct
        if fields.next() != Some("cgroup") {
            return None;
        }

        // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup <rw,cpu,cpuacct>
        let super_opts = some!(fields.nth(1));

        // We only care about the 'cpu' option
        if !super_opts.split(',').any(|opt| opt == "cpu") {
            return None;
        }

        Some(MountInfo {
            root: mnt_root.to_owned(),
            mount_point: mnt_point.to_owned(),
        })
    }
}

impl Subsys {
    fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<Subsys> {
        let file = some!(File::open(proc_path).ok());
        let file = BufReader::new(file);

        file.lines()
            .filter_map(|result| result.ok())
            .filter_map(Subsys::parse_line)
            .next()
    }

    fn parse_line(line: String) -> Option<Subsys> {
        // Example format:
        // 11:cpu,cpuacct:/
        let mut fields = line.split(':');

        let sub_systems = some!(fields.nth(1));

        if !sub_systems.split(',').any(|sub| sub == "cpu") {
            return None;
        }

        fields.next().map(|path| Subsys {
            base: path.to_owned(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::{Cgroup, MountInfo, Subsys};
    use std::path::{Path, PathBuf};

    // `static_in_const` feature is not stable in Rust 1.13.
    static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups";

    static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups";

    macro_rules! join {
        ($base:expr, $($path:expr),+) => ({
            Path::new($base)
                $(.join($path))+
        })
    }

    #[test]
    fn test_load_mountinfo() {
        // test only one optional fields
        let path = join!(FIXTURES_PROC, "mountinfo");

        let mnt_info = MountInfo::load_cpu(path).unwrap();

        assert_eq!(mnt_info.root, "/");
        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");

        // test zero optional field
        let path = join!(FIXTURES_PROC, "mountinfo_zero_opt");

        let mnt_info = MountInfo::load_cpu(path).unwrap();

        assert_eq!(mnt_info.root, "/");
        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");

        // test multi optional fields
        let path = join!(FIXTURES_PROC, "mountinfo_multi_opt");

        let mnt_info = MountInfo::load_cpu(path).unwrap();

        assert_eq!(mnt_info.root, "/");
        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
    }

    #[test]
    fn test_load_subsys() {
        let path = join!(FIXTURES_PROC, "cgroup");

        let subsys = Subsys::load_cpu(path).unwrap();

        assert_eq!(subsys.base, "/");
    }

    #[test]
    fn test_cgroup_mount() {
        let cases = &[
            ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd",
                Some("/sys/fs/cgroup/cpu"),
            ),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd/",
                Some("/sys/fs/cgroup/cpu"),
            ),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd/large",
                Some("/sys/fs/cgroup/cpu/large"),
            ),
            // fails
            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None),
            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None),
            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd-other-dir",
                None,
            ),
        ];

        for &(root, mount_point, subsys, expected) in cases.iter() {
            let mnt_info = MountInfo {
                root: root.into(),
                mount_point: mount_point.into(),
            };
            let subsys = Subsys {
                base: subsys.into(),
            };

            let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base);
            let expected = expected.map(PathBuf::from);
            assert_eq!(actual, expected);
        }
    }

    #[test]
    fn test_cgroup_cpu_quota() {
        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "good"));
        assert_eq!(cgroup.cpu_quota(), Some(6));
    }

    #[test]
    fn test_cgroup_cpu_quota_divide_by_zero() {
        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "zero-period"));
        assert!(cgroup.quota_us().is_some());
        assert_eq!(cgroup.period_us(), Some(0));
        assert_eq!(cgroup.cpu_quota(), None);
    }

    #[test]
    fn test_cgroup_cpu_quota_ceil() {
        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "ceil"));
        assert_eq!(cgroup.cpu_quota(), Some(2));
    }
}
<commit_msg>do not attempt to open files in Miri (#121)<commit_after>use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Once;

use libc;

macro_rules! debug {
    ($($args:expr),*) => ({
        if false {
        //if true {
            println!($($args),*);
        }
    });
}

macro_rules! some {
    ($e:expr) => {{
        match $e {
            Some(v) => v,
            None => {
                debug!("NONE: {:?}", stringify!($e));
                return None;
            }
        }
    }};
}

pub fn get_num_cpus() -> usize {
    match cgroups_num_cpus() {
        Some(n) => n,
        None => logical_cpus(),
    }
}

fn logical_cpus() -> usize {
    let mut set: libc::cpu_set_t = unsafe { mem::zeroed() };
    if unsafe { libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) } == 0 {
        let mut count: u32 = 0;
        for i in 0..libc::CPU_SETSIZE as usize {
            if unsafe { libc::CPU_ISSET(i, &set) } {
                count += 1
            }
        }
        count as usize
    } else {
        let cpus = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) };
        if cpus < 1 {
            1
        } else {
            cpus as usize
        }
    }
}

pub fn get_num_physical_cpus() -> usize {
    let file = match File::open("/proc/cpuinfo") {
        Ok(val) => val,
        Err(_) => return get_num_cpus(),
    };
    let reader = BufReader::new(file);
    let mut map = HashMap::new();
    let mut physid: u32 = 0;
    let mut cores: usize = 0;
    let mut chgcount = 0;
    for line in reader.lines().filter_map(|result| result.ok()) {
        let mut it = line.split(':');
        let (key, value) = match (it.next(), it.next()) {
            (Some(key), Some(value)) => (key.trim(), value.trim()),
            _ => continue,
        };
        if key == "physical id" {
            match value.parse() {
                Ok(val) => physid = val,
                Err(_) => break,
            };
            chgcount += 1;
        }
        if key == "cpu cores" {
            match value.parse() {
                Ok(val) => cores = val,
                Err(_) => break,
            };
            chgcount += 1;
        }
        if chgcount == 2 {
            map.insert(physid, cores);
            chgcount = 0;
        }
    }
    let count = map.into_iter().fold(0, |acc, (_, cores)| acc + cores);

    if count == 0 {
        get_num_cpus()
    } else {
        count
    }
}

/// Cached CPUs calculated from cgroups.
///
/// If 0, check logical cpus.
// Allow deprecation warnings, we want to work on older rustc
#[allow(warnings)]
static CGROUPS_CPUS: AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;

fn cgroups_num_cpus() -> Option<usize> {
    #[allow(warnings)]
    static ONCE: Once = ::std::sync::ONCE_INIT;

    ONCE.call_once(init_cgroups);

    let cpus = CGROUPS_CPUS.load(Ordering::Acquire);

    if cpus > 0 {
        Some(cpus)
    } else {
        None
    }
}

fn init_cgroups() {
    // Should only be called once
    debug_assert!(CGROUPS_CPUS.load(Ordering::SeqCst) == 0);

    // Fails in Miri by default (cannot open files), and Miri does not have parallelism anyway.
    if cfg!(miri) {
        return;
    }

    if let Some(quota) = load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") {
        if quota == 0 {
            return;
        }

        let logical = logical_cpus();
        let count = ::std::cmp::min(quota, logical);

        CGROUPS_CPUS.store(count, Ordering::SeqCst);
    }
}

fn load_cgroups<P1, P2>(cgroup_proc: P1, mountinfo_proc: P2) -> Option<usize>
where
    P1: AsRef<Path>,
    P2: AsRef<Path>,
{
    let subsys = some!(Subsys::load_cpu(cgroup_proc));
    let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc));
    let cgroup = some!(Cgroup::translate(mntinfo, subsys));
    cgroup.cpu_quota()
}

struct Cgroup {
    base: PathBuf,
}

struct MountInfo {
    root: String,
    mount_point: String,
}

struct Subsys {
    base: String,
}

impl Cgroup {
    fn new(dir: PathBuf) -> Cgroup {
        Cgroup { base: dir }
    }

    fn translate(mntinfo: MountInfo, subsys: Subsys) -> Option<Cgroup> {
        // Translate the subsystem directory via the host paths.
        debug!(
            "subsys = {:?}; root = {:?}; mount_point = {:?}",
            subsys.base, mntinfo.root, mntinfo.mount_point
        );

        let rel_from_root = some!(Path::new(&subsys.base).strip_prefix(&mntinfo.root).ok());

        debug!("rel_from_root: {:?}", rel_from_root);

        // join(mp.MountPoint, relPath)
        let mut path = PathBuf::from(mntinfo.mount_point);
        path.push(rel_from_root);
        Some(Cgroup::new(path))
    }

    fn cpu_quota(&self) -> Option<usize> {
        let quota_us = some!(self.quota_us());
        let period_us = some!(self.period_us());

        // protect against dividing by zero
        if period_us == 0 {
            return None;
        }

        // Ceil the division, since we want to be able to saturate
        // the available CPUs, and flooring would leave a CPU un-utilized.

        Some((quota_us as f64 / period_us as f64).ceil() as usize)
    }

    fn quota_us(&self) -> Option<usize> {
        self.param("cpu.cfs_quota_us")
    }

    fn period_us(&self) -> Option<usize> {
        self.param("cpu.cfs_period_us")
    }

    fn param(&self, param: &str) -> Option<usize> {
        let mut file = some!(File::open(self.base.join(param)).ok());

        let mut buf = String::new();
        some!(file.read_to_string(&mut buf).ok());

        buf.trim().parse().ok()
    }
}

impl MountInfo {
    fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<MountInfo> {
        let file = some!(File::open(proc_path).ok());
        let file = BufReader::new(file);

        file.lines()
            .filter_map(|result| result.ok())
            .filter_map(MountInfo::parse_line)
            .next()
    }

    fn parse_line(line: String) -> Option<MountInfo> {
        let mut fields = line.split(' ');

        // 7 5 0:6 </> /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct
        let mnt_root = some!(fields.nth(3));
        // 7 5 0:6 / </sys/fs/cgroup/cpu,cpuacct> rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct
        let mnt_point = some!(fields.next());

        // Ignore all fields until the separator(-).
        // Note: there could be zero or more optional fields before hyphen.
        // See: https://man7.org/linux/man-pages/man5/proc.5.html
        // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 <-> cgroup cgroup rw,cpu,cpuacct
        // Note: we cannot use `?` here because we need to support Rust 1.13.
        match fields.find(|&s| s == "-") {
            Some(_) => {}
            None => return None,
        };

        // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - <cgroup> cgroup rw,cpu,cpuacct
        if fields.next() != Some("cgroup") {
            return None;
        }

        // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup <rw,cpu,cpuacct>
        let super_opts = some!(fields.nth(1));

        // We only care about the 'cpu' option
        if !super_opts.split(',').any(|opt| opt == "cpu") {
            return None;
        }

        Some(MountInfo {
            root: mnt_root.to_owned(),
            mount_point: mnt_point.to_owned(),
        })
    }
}

impl Subsys {
    fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<Subsys> {
        let file = some!(File::open(proc_path).ok());
        let file = BufReader::new(file);

        file.lines()
            .filter_map(|result| result.ok())
            .filter_map(Subsys::parse_line)
            .next()
    }

    fn parse_line(line: String) -> Option<Subsys> {
        // Example format:
        // 11:cpu,cpuacct:/
        let mut fields = line.split(':');

        let sub_systems = some!(fields.nth(1));

        if !sub_systems.split(',').any(|sub| sub == "cpu") {
            return None;
        }

        fields.next().map(|path| Subsys {
            base: path.to_owned(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::{Cgroup, MountInfo, Subsys};
    use std::path::{Path, PathBuf};

    // `static_in_const` feature is not stable in Rust 1.13.
    static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups";

    static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups";

    macro_rules! join {
        ($base:expr, $($path:expr),+) => ({
            Path::new($base)
                $(.join($path))+
        })
    }

    #[test]
    fn test_load_mountinfo() {
        // test only one optional fields
        let path = join!(FIXTURES_PROC, "mountinfo");

        let mnt_info = MountInfo::load_cpu(path).unwrap();

        assert_eq!(mnt_info.root, "/");
        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");

        // test zero optional field
        let path = join!(FIXTURES_PROC, "mountinfo_zero_opt");

        let mnt_info = MountInfo::load_cpu(path).unwrap();

        assert_eq!(mnt_info.root, "/");
        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");

        // test multi optional fields
        let path = join!(FIXTURES_PROC, "mountinfo_multi_opt");

        let mnt_info = MountInfo::load_cpu(path).unwrap();

        assert_eq!(mnt_info.root, "/");
        assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct");
    }

    #[test]
    fn test_load_subsys() {
        let path = join!(FIXTURES_PROC, "cgroup");

        let subsys = Subsys::load_cpu(path).unwrap();

        assert_eq!(subsys.base, "/");
    }

    #[test]
    fn test_cgroup_mount() {
        let cases = &[
            ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd",
                Some("/sys/fs/cgroup/cpu"),
            ),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd/",
                Some("/sys/fs/cgroup/cpu"),
            ),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd/large",
                Some("/sys/fs/cgroup/cpu/large"),
            ),
            // fails
            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None),
            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None),
            ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None),
            (
                "/docker/01abcd",
                "/sys/fs/cgroup/cpu",
                "/docker/01abcd-other-dir",
                None,
            ),
        ];

        for &(root, mount_point, subsys, expected) in cases.iter() {
            let mnt_info = MountInfo {
                root: root.into(),
                mount_point: mount_point.into(),
            };
            let subsys = Subsys {
                base: subsys.into(),
            };

            let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base);
            let expected = expected.map(PathBuf::from);
            assert_eq!(actual, expected);
        }
    }

    #[test]
    fn test_cgroup_cpu_quota() {
        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "good"));
        assert_eq!(cgroup.cpu_quota(), Some(6));
    }

    #[test]
    fn test_cgroup_cpu_quota_divide_by_zero() {
        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "zero-period"));
        assert!(cgroup.quota_us().is_some());
        assert_eq!(cgroup.period_us(), Some(0));
        assert_eq!(cgroup.cpu_quota(), None);
    }

    #[test]
    fn test_cgroup_cpu_quota_ceil() {
        let cgroup = Cgroup::new(join!(FIXTURES_CGROUPS, "ceil"));
        assert_eq!(cgroup.cpu_quota(), Some(2));
    }
}
<|endoftext|>


<commit_before>//! Representation of an email address

use idna::domain_to_ascii;
use once_cell::sync::Lazy;
use regex::Regex;
use std::{
    convert::{TryFrom, TryInto},
    error::Error,
    ffi::OsStr,
    fmt::{Display, Formatter, Result as FmtResult},
    net::IpAddr,
    str::FromStr,
};

/// Represents an email address with a user and a domain name.
///
/// This type contains email in canonical form (_user@domain.tld_).
///
/// **NOTE**: Enable feature "serde" to be able serialize/deserialize it using [serde](https://serde.rs/).
///
/// # Examples
///
/// You can create an `Address` from a user and a domain:
///
/// ```
/// use lettre::Address;
///
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let address = Address::new("user", "email.com")?;
/// assert_eq!(address.user(), "user");
/// assert_eq!(address.domain(), "email.com");
/// # Ok(())
/// # }
/// ```
///
/// You can also create an `Address` from a string literal by parsing it:
///
/// ```
/// use lettre::Address;
///
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let address = "user@email.com".parse::<Address>()?;
/// assert_eq!(address.user(), "user");
/// assert_eq!(address.domain(), "email.com");
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Address {
    /// Complete address
    serialized: String,
    /// Index into `serialized` before the '@'
    at_start: usize,
}

// Regex from the specs
// https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
// It will mark esoteric email addresses like quoted string as invalid
static USER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"^(?i)[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z").unwrap());
static DOMAIN_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(
        r"(?i)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$",
    )
    .unwrap()
});
// literal form, ipv4 or ipv6 address (SMTP 4.1.3)
static LITERAL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\[([A-f0-9:\.]+)\]\z").unwrap());

impl Address {
    /// Creates a new email address from a user and domain.
    ///
    /// # Examples
    ///
    /// ```
    /// use lettre::Address;
    ///
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// let address = Address::new("user", "email.com")?;
    /// let expected = "user@email.com".parse::<Address>()?;
    /// assert_eq!(expected, address);
    /// # Ok(())
    /// # }
    /// ```
    pub fn new<U: AsRef<str>, D: AsRef<str>>(user: U, domain: D) -> Result<Self, AddressError> {
        (user, domain).try_into()
    }

    /// Gets the user portion of the `Address`.
    ///
    /// # Examples
    ///
    /// ```
    /// use lettre::Address;
    ///
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// let address = Address::new("user", "email.com")?;
    /// assert_eq!(address.user(), "user");
    /// # Ok(())
    /// # }
    /// ```
    pub fn user(&self) -> &str {
        &self.serialized[..self.at_start]
    }

    /// Gets the domain portion of the `Address`.
    ///
    /// # Examples
    ///
    /// ```
    /// use lettre::Address;
    ///
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// let address = Address::new("user", "email.com")?;
    /// assert_eq!(address.domain(), "email.com");
    /// # Ok(())
    /// # }
    /// ```
    pub fn domain(&self) -> &str {
        &self.serialized[self.at_start + 1..]
    }

    pub(super) fn check_user(user: &str) -> Result<(), AddressError> {
        if USER_RE.is_match(user) {
            Ok(())
        } else {
            Err(AddressError::InvalidUser)
        }
    }

    pub(super) fn check_domain(domain: &str) -> Result<(), AddressError> {
        Address::check_domain_ascii(domain).or_else(|_| {
            domain_to_ascii(domain)
                .map_err(|_| AddressError::InvalidDomain)
                .and_then(|domain| Address::check_domain_ascii(&domain))
        })
    }

    fn check_domain_ascii(domain: &str) -> Result<(), AddressError> {
        if DOMAIN_RE.is_match(domain) {
            return Ok(());
        }

        if let Some(caps) = LITERAL_RE.captures(domain) {
            if let Some(cap) = caps.get(1) {
                if cap.as_str().parse::<IpAddr>().is_ok() {
                    return Ok(());
                }
            }
        }

        Err(AddressError::InvalidDomain)
    }

    #[cfg(feature = "smtp-transport")]
    /// Check if the address contains non-ascii chars
    pub(super) fn is_ascii(&self) -> bool {
        self.serialized.is_ascii()
    }
}

impl Display for Address {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(&self.serialized)
    }
}

impl FromStr for Address {
    type Err = AddressError;

    fn from_str(val: &str) -> Result<Self, AddressError> {
        let mut parts = val.rsplitn(2, '@');
        let domain = parts.next().ok_or(AddressError::MissingParts)?;
        let user = parts.next().ok_or(AddressError::MissingParts)?;

        Address::check_user(user)?;
        Address::check_domain(domain)?;
        Ok(Address {
            serialized: val.into(),
            at_start: user.len(),
        })
    }
}

impl<U, D> TryFrom<(U, D)> for Address
where
    U: AsRef<str>,
    D: AsRef<str>,
{
    type Error = AddressError;

    fn try_from((user, domain): (U, D)) -> Result<Self, Self::Error> {
        let user = user.as_ref();
        Address::check_user(user)?;

        let domain = domain.as_ref();
        Address::check_domain(domain)?;

        let serialized = format!("{}@{}", user, domain);
        Ok(Address {
            serialized,
            at_start: user.len(),
        })
    }
}

impl AsRef<str> for Address {
    fn as_ref(&self) -> &str {
        &self.serialized
    }
}

impl AsRef<OsStr> for Address {
    fn as_ref(&self) -> &OsStr {
        self.serialized.as_ref()
    }
}

#[derive(Debug, PartialEq, Clone, Copy)]
/// Errors in email addresses parsing
pub enum AddressError {
    /// Missing domain or user
    MissingParts,
    /// Unbalanced angle bracket
    Unbalanced,
    /// Invalid email user
    InvalidUser,
    /// Invalid email domain
    InvalidDomain,
}

impl Error for AddressError {}

impl Display for AddressError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            AddressError::MissingParts => f.write_str("Missing domain or user"),
            AddressError::Unbalanced => f.write_str("Unbalanced angle bracket"),
            AddressError::InvalidUser => f.write_str("Invalid email user"),
            AddressError::InvalidDomain => f.write_str("Invalid email domain"),
        }
    }
}

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

    #[test]
    fn parse_address() {
        let addr_str = "something@example.com";
        let addr = Address::from_str(addr_str).unwrap();
        let addr2 = Address::new("something", "example.com").unwrap();
        assert_eq!(addr, addr2);
        assert_eq!(addr.user(), "something");
        assert_eq!(addr.domain(), "example.com");
        assert_eq!(addr2.user(), "something");
        assert_eq!(addr2.domain(), "example.com");
    }
}
<commit_msg>feat(address): Add TryFrom<String> for Address (#660)<commit_after>//! Representation of an email address

use idna::domain_to_ascii;
use once_cell::sync::Lazy;
use regex::Regex;
use std::{
    convert::{TryFrom, TryInto},
    error::Error,
    ffi::OsStr,
    fmt::{Display, Formatter, Result as FmtResult},
    net::IpAddr,
    str::FromStr,
};

/// Represents an email address with a user and a domain name.
///
/// This type contains email in canonical form (_user@domain.tld_).
///
/// **NOTE**: Enable feature "serde" to be able serialize/deserialize it using [serde](https://serde.rs/).
///
/// # Examples
///
/// You can create an `Address` from a user and a domain:
///
/// ```
/// use lettre::Address;
///
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let address = Address::new("user", "email.com")?;
/// assert_eq!(address.user(), "user");
/// assert_eq!(address.domain(), "email.com");
/// # Ok(())
/// # }
/// ```
///
/// You can also create an `Address` from a string literal by parsing it:
///
/// ```
/// use lettre::Address;
///
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let address = "user@email.com".parse::<Address>()?;
/// assert_eq!(address.user(), "user");
/// assert_eq!(address.domain(), "email.com");
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Address {
    /// Complete address
    serialized: String,
    /// Index into `serialized` before the '@'
    at_start: usize,
}

// Regex from the specs
// https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
// It will mark esoteric email addresses like quoted string as invalid
static USER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"^(?i)[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z").unwrap());
static DOMAIN_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(
        r"(?i)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$",
    )
    .unwrap()
});
// literal form, ipv4 or ipv6 address (SMTP 4.1.3)
static LITERAL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\[([A-f0-9:\.]+)\]\z").unwrap());

impl Address {
    /// Creates a new email address from a user and domain.
    ///
    /// # Examples
    ///
    /// ```
    /// use lettre::Address;
    ///
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// let address = Address::new("user", "email.com")?;
    /// let expected = "user@email.com".parse::<Address>()?;
    /// assert_eq!(expected, address);
    /// # Ok(())
    /// # }
    /// ```
    pub fn new<U: AsRef<str>, D: AsRef<str>>(user: U, domain: D) -> Result<Self, AddressError> {
        (user, domain).try_into()
    }

    /// Gets the user portion of the `Address`.
    ///
    /// # Examples
    ///
    /// ```
    /// use lettre::Address;
    ///
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// let address = Address::new("user", "email.com")?;
    /// assert_eq!(address.user(), "user");
    /// # Ok(())
    /// # }
    /// ```
    pub fn user(&self) -> &str {
        &self.serialized[..self.at_start]
    }

    /// Gets the domain portion of the `Address`.
    ///
    /// # Examples
    ///
    /// ```
    /// use lettre::Address;
    ///
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// let address = Address::new("user", "email.com")?;
    /// assert_eq!(address.domain(), "email.com");
    /// # Ok(())
    /// # }
    /// ```
    pub fn domain(&self) -> &str {
        &self.serialized[self.at_start + 1..]
    }

    pub(super) fn check_user(user: &str) -> Result<(), AddressError> {
        if USER_RE.is_match(user) {
            Ok(())
        } else {
            Err(AddressError::InvalidUser)
        }
    }

    pub(super) fn check_domain(domain: &str) -> Result<(), AddressError> {
        Address::check_domain_ascii(domain).or_else(|_| {
            domain_to_ascii(domain)
                .map_err(|_| AddressError::InvalidDomain)
                .and_then(|domain| Address::check_domain_ascii(&domain))
        })
    }

    fn check_domain_ascii(domain: &str) -> Result<(), AddressError> {
        if DOMAIN_RE.is_match(domain) {
            return Ok(());
        }

        if let Some(caps) = LITERAL_RE.captures(domain) {
            if let Some(cap) = caps.get(1) {
                if cap.as_str().parse::<IpAddr>().is_ok() {
                    return Ok(());
                }
            }
        }

        Err(AddressError::InvalidDomain)
    }

    #[cfg(feature = "smtp-transport")]
    /// Check if the address contains non-ascii chars
    pub(super) fn is_ascii(&self) -> bool {
        self.serialized.is_ascii()
    }
}

impl Display for Address {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(&self.serialized)
    }
}

impl FromStr for Address {
    type Err = AddressError;

    fn from_str(val: &str) -> Result<Self, AddressError> {
        let at_start = check_address(val)?;
        Ok(Address {
            serialized: val.into(),
            at_start,
        })
    }
}

impl<U, D> TryFrom<(U, D)> for Address
where
    U: AsRef<str>,
    D: AsRef<str>,
{
    type Error = AddressError;

    fn try_from((user, domain): (U, D)) -> Result<Self, Self::Error> {
        let user = user.as_ref();
        Address::check_user(user)?;

        let domain = domain.as_ref();
        Address::check_domain(domain)?;

        let serialized = format!("{}@{}", user, domain);
        Ok(Address {
            serialized,
            at_start: user.len(),
        })
    }
}

impl TryFrom<String> for Address {
    type Error = AddressError;

    fn try_from(serialized: String) -> Result<Self, AddressError> {
        let at_start = check_address(&serialized)?;
        Ok(Address {
            serialized,
            at_start,
        })
    }
}

impl AsRef<str> for Address {
    fn as_ref(&self) -> &str {
        &self.serialized
    }
}

impl AsRef<OsStr> for Address {
    fn as_ref(&self) -> &OsStr {
        self.serialized.as_ref()
    }
}

fn check_address(val: &str) -> Result<usize, AddressError> {
    let mut parts = val.rsplitn(2, '@');
    let domain = parts.next().ok_or(AddressError::MissingParts)?;
    let user = parts.next().ok_or(AddressError::MissingParts)?;

    Address::check_user(user)?;
    Address::check_domain(domain)?;
    Ok(user.len())
}

#[derive(Debug, PartialEq, Clone, Copy)]
/// Errors in email addresses parsing
pub enum AddressError {
    /// Missing domain or user
    MissingParts,
    /// Unbalanced angle bracket
    Unbalanced,
    /// Invalid email user
    InvalidUser,
    /// Invalid email domain
    InvalidDomain,
}

impl Error for AddressError {}

impl Display for AddressError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            AddressError::MissingParts => f.write_str("Missing domain or user"),
            AddressError::Unbalanced => f.write_str("Unbalanced angle bracket"),
            AddressError::InvalidUser => f.write_str("Invalid email user"),
            AddressError::InvalidDomain => f.write_str("Invalid email domain"),
        }
    }
}

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

    #[test]
    fn parse_address() {
        let addr_str = "something@example.com";
        let addr = Address::from_str(addr_str).unwrap();
        let addr2 = Address::new("something", "example.com").unwrap();
        assert_eq!(addr, addr2);
        assert_eq!(addr.user(), "something");
        assert_eq!(addr.domain(), "example.com");
        assert_eq!(addr2.user(), "something");
        assert_eq!(addr2.domain(), "example.com");
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

//! Various alignment algorithms.

pub mod pairwise;

/// Alignment operations (Match, Subst, Del and Ins).
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum AlignmentOperation {
    Match,
    Subst,
    Del,
    Ins
}

/// An alignment, consisting of a score, a start in sequence y, a start in sequence x, a length
/// and its edit operations (see alignment::pairwise for meaning of x and y).
#[derive(Debug)]
pub struct Alignment {
    pub score: i32,
    pub ystart: usize,
    pub xstart: usize,
    pub xlen: usize,
    pub operations: Vec<AlignmentOperation>
}

impl Alignment {
    /// Calculate the cigar string.
    pub fn cigar(&self, hard_clip: bool) -> String {
        let add_op = |op, k, cigar: &mut String| {
            cigar.push_str(&format!("{}{}", k, match op {
                AlignmentOperation::Match => "=",
                AlignmentOperation::Subst => "X",
                AlignmentOperation::Del => "D",
                AlignmentOperation::Ins => "I",
            }));
        };

        let op_len = |op: AlignmentOperation| {
            (op == AlignmentOperation::Match || op == AlignmentOperation::Subst || op == AlignmentOperation::Ins) as usize
        };

        let clip_str = if hard_clip {"H"} else {"S"};

        let mut cigar = String::new();

        if !self.operations.is_empty() {
            if self.xstart > 0 {
                cigar.push_str(&format!("{}{}", self.xstart, clip_str));
            }

            let mut last = self.operations[0];
            let mut k = 1;
            let mut alen = op_len(last);
            for &op in self.operations[1..].iter() {
                if op == last {
                    k += 1;
                } else {
                    add_op(last, k, &mut cigar);
                    k = 1;
                }
                last = op;
                alen += op_len(op);
            }
            add_op(last, k, &mut cigar);

            let clip = self.xlen - alen;
            if clip > 0 {
                cigar.push_str(&format!("{}{}", clip, clip_str));
            }
        }
        else {
            cigar.push_str(&format!("{}{}", self.xlen, clip_str));
        }

        cigar
    }
}

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

    #[test]
    fn test_cigar() {
        let alignment = Alignment { score: 5, xstart: 3, ystart: 0, xlen: 10, operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del] };
        assert_eq!(alignment.cigar(false), "3S3=1X2I2D4S");
    }
}
<commit_msg>add a draft code for the pretty output of alignments
<commit_after>// Copyright 2014 Johannes Köster.
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

//! Various alignment algorithms.

pub mod pairwise;

/// Alignment operations (Match, Subst, Del and Ins).
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum AlignmentOperation {
    Match,
    Subst,
    Del,
    Ins
}

/// An alignment, consisting of a score, a start in sequence y, a start in sequence x, a length
/// and its edit operations (see alignment::pairwise for meaning of x and y).
#[derive(Debug)]
pub struct Alignment {
    pub score: i32,
    pub ystart: usize,
    pub xstart: usize,
    pub xlen: usize,
    pub operations: Vec<AlignmentOperation>
}

impl Alignment {
    /// Calculate the cigar string.
    pub fn cigar(&self, hard_clip: bool) -> String {
        let add_op = |op, k, cigar: &mut String| {
            cigar.push_str(&format!("{}{}", k, match op {
                AlignmentOperation::Match => "=",
                AlignmentOperation::Subst => "X",
                AlignmentOperation::Del => "D",
                AlignmentOperation::Ins => "I",
            }));
        };

        let op_len = |op: AlignmentOperation| {
            (op == AlignmentOperation::Match || op == AlignmentOperation::Subst || op == AlignmentOperation::Ins) as usize
        };

        let clip_str = if hard_clip {"H"} else {"S"};

        let mut cigar = String::new();

        if !self.operations.is_empty() {
            if self.xstart > 0 {
                cigar.push_str(&format!("{}{}", self.xstart, clip_str));
            }

            let mut last = self.operations[0];
            let mut k = 1;
            let mut alen = op_len(last);
            for &op in self.operations[1..].iter() {
                if op == last {
                    k += 1;
                } else {
                    add_op(last, k, &mut cigar);
                    k = 1;
                }
                last = op;
                alen += op_len(op);
            }
            add_op(last, k, &mut cigar);

            let clip = self.xlen - alen;
            if clip > 0 {
                cigar.push_str(&format!("{}{}", clip, clip_str));
            }
        }
        else {
            cigar.push_str(&format!("{}{}", self.xlen, clip_str));
        }

        cigar
    }

    /// Return the pretty formatted alignment as a string.
    pub fn pretty(&self, x: &[u8], y: &[u8]) -> String {
        let mut x_pretty = String::new();
        let mut y_pretty = String::new();
        let mut inb_pretty = String::new();

        if !self.operations.is_empty() {
            let mut x_add = &[u8];
            let mut y_add = String::new();
            let mut inb_add = String::new();

            for i in 1..self.operations.len() {
                match self.operations[i] {
                    AlignmentOperation::Match => {
                        x_add = &x[i];
                        inb_add = "|";
                        y_add = y[i];
                    },
                    AlignmentOperation::Subst => {
                        x_add = x[i];
                        inb_add = " ";
                        y_add = y[i];
                    },
                    AlignmentOperation::Del => {
                        x_add = "-";
                        inb_add = " ";
                        y_add = y[i];
                    },
                    AlignmentOperation::Ins => {
                        x_add = x[i];
                        inb_add = " ";
                        y_add = "-";
                    },
                }

                x_pretty.push_str(x_add);
                inb_pretty.push_str(inb_add);
                y_pretty.push_str(y_add);
            }
        }

        format!("{}\n{}\n{}", x_pretty, inb_pretty, y_pretty)
    }
}

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

    #[test]
    fn test_cigar() {
        let alignment = Alignment { score: 5, xstart: 3, ystart: 0, xlen: 10, operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del] };
        assert_eq!(alignment.cigar(false), "3S3=1X2I2D4S");
    }
}
<|endoftext|>


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

use std::{convert::TryFrom, mem};

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident};

use crate::api::{
    attribute::{Meta, MetaNameValue},
    strip_serde_attrs, RawResponse,
};

/// The result of processing the `response` section of the macro.
pub struct Response {
    /// The fields of the response.
    fields: Vec<ResponseField>,
}

impl Response {
    /// Whether or not this response has any data in the HTTP body.
    pub fn has_body_fields(&self) -> bool {
        self.fields.iter().any(|field| field.is_body())
    }

    /// Whether or not this response has any data in HTTP headers.
    pub fn has_header_fields(&self) -> bool {
        self.fields.iter().any(|field| field.is_header())
    }

    /// Whether any field has a #[wrap_incoming] attribute.
    pub fn uses_wrap_incoming(&self) -> bool {
        self.fields.iter().any(|f| f.has_wrap_incoming_attr())
    }

    /// Produces code for a response struct initializer.
    pub fn init_fields(&self) -> TokenStream {
        let fields = self.fields.iter().map(|response_field| match response_field {
            ResponseField::Body(field) => {
                let field_name =
                    field.ident.as_ref().expect("expected field to have an identifier");
                let span = field.span();

                quote_spanned! {span=>
                    #field_name: response_body.#field_name
                }
            }
            ResponseField::Header(field, header_name) => {
                let field_name =
                    field.ident.as_ref().expect("expected field to have an identifier");
                let span = field.span();

                quote_spanned! {span=>
                    #field_name: headers.remove(ruma_api::exports::http::header::#header_name)
                        .expect("response missing expected header")
                        .to_str()
                        .expect("failed to convert HeaderValue to str")
                        .to_owned()
                }
            }
            ResponseField::NewtypeBody(field) => {
                let field_name =
                    field.ident.as_ref().expect("expected field to have an identifier");
                let span = field.span();

                quote_spanned! {span=>
                    #field_name: response_body.0
                }
            }
        });

        quote! {
            #(#fields,)*
        }
    }

    /// Produces code to add necessary HTTP headers to an `http::Response`.
    pub fn apply_header_fields(&self) -> TokenStream {
        let header_calls = self.fields.iter().filter_map(|response_field| {
            if let ResponseField::Header(ref field, ref header_name) = *response_field {
                let field_name =
                    field.ident.as_ref().expect("expected field to have an identifier");
                let span = field.span();

                Some(quote_spanned! {span=>
                    .header(ruma_api::exports::http::header::#header_name, response.#field_name)
                })
            } else {
                None
            }
        });

        quote! { #(#header_calls)* }
    }

    /// Produces code to initialize the struct that will be used to create the response body.
    pub fn to_body(&self) -> TokenStream {
        if let Some(field) = self.newtype_body_field() {
            let field_name = field.ident.as_ref().expect("expected field to have an identifier");
            let span = field.span();
            quote_spanned!(span=> response.#field_name)
        } else {
            let fields = self.fields.iter().filter_map(|response_field| {
                if let ResponseField::Body(ref field) = *response_field {
                    let field_name =
                        field.ident.as_ref().expect("expected field to have an identifier");
                    let span = field.span();

                    Some(quote_spanned! {span=>
                        #field_name: response.#field_name
                    })
                } else {
                    None
                }
            });

            quote! {
                ResponseBody { #(#fields),* }
            }
        }
    }

    /// Gets the newtype body field, if this response has one.
    pub fn newtype_body_field(&self) -> Option<&Field> {
        self.fields.iter().find_map(ResponseField::as_newtype_body_field)
    }
}

impl TryFrom<RawResponse> for Response {
    type Error = syn::Error;

    fn try_from(raw: RawResponse) -> syn::Result<Self> {
        let mut newtype_body_field = None;

        let fields = raw
            .fields
            .into_iter()
            .map(|mut field| {
                let mut field_kind = None;
                let mut header = None;

                for attr in mem::replace(&mut field.attrs, Vec::new()) {
                    let meta = match Meta::from_attribute(&attr)? {
                        Some(m) => m,
                        None => {
                            field.attrs.push(attr);
                            continue;
                        }
                    };

                    if field_kind.is_some() {
                        return Err(syn::Error::new_spanned(
                            attr,
                            "There can only be one field kind attribute",
                        ));
                    }

                    field_kind = Some(match meta {
                        Meta::Word(ident) => {
                            if ident != "body" {
                                return Err(syn::Error::new_spanned(
                                    ident,
                                    "Invalid #[ruma_api] argument with value, expected `body`",
                                ));
                            }

                            if let Some(f) = &newtype_body_field {
                                let mut error = syn::Error::new_spanned(
                                    field,
                                    "There can only be one newtype body field",
                                );
                                error.combine(syn::Error::new_spanned(
                                    f,
                                    "Previous newtype body field",
                                ));
                                return Err(error);
                            }

                            newtype_body_field = Some(field.clone());
                            ResponseFieldKind::NewtypeBody
                        }
                        Meta::NameValue(MetaNameValue { name, value }) => {
                            if name != "header" {
                                return Err(syn::Error::new_spanned(
                                    name,
                                    "Invalid #[ruma_api] argument with value, expected `header`",
                                ));
                            }

                            header = Some(value);
                            ResponseFieldKind::Header
                        }
                    });
                }

                Ok(match field_kind.unwrap_or(ResponseFieldKind::Body) {
                    ResponseFieldKind::Body => ResponseField::Body(field),
                    ResponseFieldKind::Header => {
                        ResponseField::Header(field, header.expect("missing header name"))
                    }
                    ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
                })
            })
            .collect::<syn::Result<Vec<_>>>()?;

        if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
            // TODO: highlight conflicting fields,
            return Err(syn::Error::new_spanned(
                raw.response_kw,
                "Can't have both a newtype body field and regular body fields",
            ));
        }

        Ok(Self { fields })
    }
}

impl ToTokens for Response {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let response_def = if self.fields.is_empty() {
            quote!(;)
        } else {
            let fields =
                self.fields.iter().map(|response_field| strip_serde_attrs(response_field.field()));

            quote! { { #(#fields),* } }
        };

        let (derive_deserialize, response_body_def) =
            if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) {
                let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
                let derive_deserialize = if body_field.has_wrap_incoming_attr() {
                    TokenStream::new()
                } else {
                    quote!(ruma_api::exports::serde::Deserialize)
                };

                (derive_deserialize, quote! { (#field); })
            } else if self.has_body_fields() {
                let fields = self.fields.iter().filter(|f| f.is_body());
                let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) {
                    TokenStream::new()
                } else {
                    quote!(ruma_api::exports::serde::Deserialize)
                };
                let fields = fields.map(ResponseField::field);

                (derive_deserialize, quote!({ #(#fields),* }))
            } else {
                (quote!(ruma_api::exports::serde::Deserialize), quote!(;))
            };

        let response = quote! {
            #[derive(Debug, Clone, ruma_api::Outgoing)]
            #[incoming_no_deserialize]
            pub struct Response #response_def

            /// Data in the response body.
            #[derive(
                Debug,
                ruma_api::Outgoing,
                ruma_api::exports::serde::Serialize,
                #derive_deserialize
            )]
            struct ResponseBody #response_body_def
        };

        response.to_tokens(tokens);
    }
}

/// The types of fields that a response can have.
pub enum ResponseField {
    /// JSON data in the body of the response.
    Body(Field),
    /// Data in an HTTP header.
    Header(Field, Ident),
    /// A specific data type in the body of the response.
    NewtypeBody(Field),
}

impl ResponseField {
    /// Gets the inner `Field` value.
    fn field(&self) -> &Field {
        match self {
            ResponseField::Body(field)
            | ResponseField::Header(field, _)
            | ResponseField::NewtypeBody(field) => field,
        }
    }

    /// Whether or not this response field is a body kind.
    fn is_body(&self) -> bool {
        self.as_body_field().is_some()
    }

    /// Whether or not this response field is a header kind.
    fn is_header(&self) -> bool {
        match self {
            ResponseField::Header(..) => true,
            _ => false,
        }
    }

    /// Whether or not this response field is a newtype body kind.
    fn is_newtype_body(&self) -> bool {
        self.as_newtype_body_field().is_some()
    }

    /// Return the contained field if this response field is a body kind.
    fn as_body_field(&self) -> Option<&Field> {
        match self {
            ResponseField::Body(field) => Some(field),
            _ => None,
        }
    }

    /// Return the contained field if this response field is a newtype body kind.
    fn as_newtype_body_field(&self) -> Option<&Field> {
        match self {
            ResponseField::NewtypeBody(field) => Some(field),
            _ => None,
        }
    }

    /// Whether or not the reponse field has a #[wrap_incoming] attribute.
    fn has_wrap_incoming_attr(&self) -> bool {
        self.field().attrs.iter().any(|attr| {
            attr.path.segments.len() == 1 && attr.path.segments[0].ident == "wrap_incoming"
        })
    }
}

/// The types of fields that a response can have, without their values.
enum ResponseFieldKind {
    /// See the similarly named variant of `ResponseField`.
    Body,
    /// See the similarly named variant of `ResponseField`.
    Header,
    /// See the similarly named variant of `ResponseField`.
    NewtypeBody,
}
<commit_msg>ruma_client_api: Simplify Response::init_fields<commit_after>//! Details of the `response` section of the procedural macro.

use std::{convert::TryFrom, mem};

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident};

use crate::api::{
    attribute::{Meta, MetaNameValue},
    strip_serde_attrs, RawResponse,
};

/// The result of processing the `response` section of the macro.
pub struct Response {
    /// The fields of the response.
    fields: Vec<ResponseField>,
}

impl Response {
    /// Whether or not this response has any data in the HTTP body.
    pub fn has_body_fields(&self) -> bool {
        self.fields.iter().any(|field| field.is_body())
    }

    /// Whether or not this response has any data in HTTP headers.
    pub fn has_header_fields(&self) -> bool {
        self.fields.iter().any(|field| field.is_header())
    }

    /// Whether any field has a #[wrap_incoming] attribute.
    pub fn uses_wrap_incoming(&self) -> bool {
        self.fields.iter().any(|f| f.has_wrap_incoming_attr())
    }

    /// Produces code for a response struct initializer.
    pub fn init_fields(&self) -> TokenStream {
        let fields = self.fields.iter().map(|response_field| {
            let field = response_field.field();
            let field_name = field.ident.as_ref().expect("expected field to have an identifier");
            let span = field.span();

            match response_field {
                ResponseField::Body(_) => {
                    quote_spanned! {span=>
                        #field_name: response_body.#field_name
                    }
                }
                ResponseField::Header(_, header_name) => {
                    quote_spanned! {span=>
                        #field_name: headers.remove(ruma_api::exports::http::header::#header_name)
                            .expect("response missing expected header")
                            .to_str()
                            .expect("failed to convert HeaderValue to str")
                            .to_owned()
                    }
                }
                ResponseField::NewtypeBody(_) => {
                    quote_spanned! {span=>
                        #field_name: response_body.0
                    }
                }
            }
        });

        quote! {
            #(#fields,)*
        }
    }

    /// Produces code to add necessary HTTP headers to an `http::Response`.
    pub fn apply_header_fields(&self) -> TokenStream {
        let header_calls = self.fields.iter().filter_map(|response_field| {
            if let ResponseField::Header(ref field, ref header_name) = *response_field {
                let field_name =
                    field.ident.as_ref().expect("expected field to have an identifier");
                let span = field.span();

                Some(quote_spanned! {span=>
                    .header(ruma_api::exports::http::header::#header_name, response.#field_name)
                })
            } else {
                None
            }
        });

        quote! { #(#header_calls)* }
    }

    /// Produces code to initialize the struct that will be used to create the response body.
    pub fn to_body(&self) -> TokenStream {
        if let Some(field) = self.newtype_body_field() {
            let field_name = field.ident.as_ref().expect("expected field to have an identifier");
            let span = field.span();
            quote_spanned!(span=> response.#field_name)
        } else {
            let fields = self.fields.iter().filter_map(|response_field| {
                if let ResponseField::Body(ref field) = *response_field {
                    let field_name =
                        field.ident.as_ref().expect("expected field to have an identifier");
                    let span = field.span();

                    Some(quote_spanned! {span=>
                        #field_name: response.#field_name
                    })
                } else {
                    None
                }
            });

            quote! {
                ResponseBody { #(#fields),* }
            }
        }
    }

    /// Gets the newtype body field, if this response has one.
    pub fn newtype_body_field(&self) -> Option<&Field> {
        self.fields.iter().find_map(ResponseField::as_newtype_body_field)
    }
}

impl TryFrom<RawResponse> for Response {
    type Error = syn::Error;

    fn try_from(raw: RawResponse) -> syn::Result<Self> {
        let mut newtype_body_field = None;

        let fields = raw
            .fields
            .into_iter()
            .map(|mut field| {
                let mut field_kind = None;
                let mut header = None;

                for attr in mem::replace(&mut field.attrs, Vec::new()) {
                    let meta = match Meta::from_attribute(&attr)? {
                        Some(m) => m,
                        None => {
                            field.attrs.push(attr);
                            continue;
                        }
                    };

                    if field_kind.is_some() {
                        return Err(syn::Error::new_spanned(
                            attr,
                            "There can only be one field kind attribute",
                        ));
                    }

                    field_kind = Some(match meta {
                        Meta::Word(ident) => {
                            if ident != "body" {
                                return Err(syn::Error::new_spanned(
                                    ident,
                                    "Invalid #[ruma_api] argument with value, expected `body`",
                                ));
                            }

                            if let Some(f) = &newtype_body_field {
                                let mut error = syn::Error::new_spanned(
                                    field,
                                    "There can only be one newtype body field",
                                );
                                error.combine(syn::Error::new_spanned(
                                    f,
                                    "Previous newtype body field",
                                ));
                                return Err(error);
                            }

                            newtype_body_field = Some(field.clone());
                            ResponseFieldKind::NewtypeBody
                        }
                        Meta::NameValue(MetaNameValue { name, value }) => {
                            if name != "header" {
                                return Err(syn::Error::new_spanned(
                                    name,
                                    "Invalid #[ruma_api] argument with value, expected `header`",
                                ));
                            }

                            header = Some(value);
                            ResponseFieldKind::Header
                        }
                    });
                }

                Ok(match field_kind.unwrap_or(ResponseFieldKind::Body) {
                    ResponseFieldKind::Body => ResponseField::Body(field),
                    ResponseFieldKind::Header => {
                        ResponseField::Header(field, header.expect("missing header name"))
                    }
                    ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
                })
            })
            .collect::<syn::Result<Vec<_>>>()?;

        if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
            // TODO: highlight conflicting fields,
            return Err(syn::Error::new_spanned(
                raw.response_kw,
                "Can't have both a newtype body field and regular body fields",
            ));
        }

        Ok(Self { fields })
    }
}

impl ToTokens for Response {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let response_def = if self.fields.is_empty() {
            quote!(;)
        } else {
            let fields =
                self.fields.iter().map(|response_field| strip_serde_attrs(response_field.field()));

            quote! { { #(#fields),* } }
        };

        let (derive_deserialize, response_body_def) =
            if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) {
                let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
                let derive_deserialize = if body_field.has_wrap_incoming_attr() {
                    TokenStream::new()
                } else {
                    quote!(ruma_api::exports::serde::Deserialize)
                };

                (derive_deserialize, quote! { (#field); })
            } else if self.has_body_fields() {
                let fields = self.fields.iter().filter(|f| f.is_body());
                let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) {
                    TokenStream::new()
                } else {
                    quote!(ruma_api::exports::serde::Deserialize)
                };
                let fields = fields.map(ResponseField::field);

                (derive_deserialize, quote!({ #(#fields),* }))
            } else {
                (quote!(ruma_api::exports::serde::Deserialize), quote!(;))
            };

        let response = quote! {
            #[derive(Debug, Clone, ruma_api::Outgoing)]
            #[incoming_no_deserialize]
            pub struct Response #response_def

            /// Data in the response body.
            #[derive(
                Debug,
                ruma_api::Outgoing,
                ruma_api::exports::serde::Serialize,
                #derive_deserialize
            )]
            struct ResponseBody #response_body_def
        };

        response.to_tokens(tokens);
    }
}

/// The types of fields that a response can have.
pub enum ResponseField {
    /// JSON data in the body of the response.
    Body(Field),
    /// Data in an HTTP header.
    Header(Field, Ident),
    /// A specific data type in the body of the response.
    NewtypeBody(Field),
}

impl ResponseField {
    /// Gets the inner `Field` value.
    fn field(&self) -> &Field {
        match self {
            ResponseField::Body(field)
            | ResponseField::Header(field, _)
            | ResponseField::NewtypeBody(field) => field,
        }
    }

    /// Whether or not this response field is a body kind.
    fn is_body(&self) -> bool {
        self.as_body_field().is_some()
    }

    /// Whether or not this response field is a header kind.
    fn is_header(&self) -> bool {
        match self {
            ResponseField::Header(..) => true,
            _ => false,
        }
    }

    /// Whether or not this response field is a newtype body kind.
    fn is_newtype_body(&self) -> bool {
        self.as_newtype_body_field().is_some()
    }

    /// Return the contained field if this response field is a body kind.
    fn as_body_field(&self) -> Option<&Field> {
        match self {
            ResponseField::Body(field) => Some(field),
            _ => None,
        }
    }

    /// Return the contained field if this response field is a newtype body kind.
    fn as_newtype_body_field(&self) -> Option<&Field> {
        match self {
            ResponseField::NewtypeBody(field) => Some(field),
            _ => None,
        }
    }

    /// Whether or not the reponse field has a #[wrap_incoming] attribute.
    fn has_wrap_incoming_attr(&self) -> bool {
        self.field().attrs.iter().any(|attr| {
            attr.path.segments.len() == 1 && attr.path.segments[0].ident == "wrap_incoming"
        })
    }
}

/// The types of fields that a response can have, without their values.
enum ResponseFieldKind {
    /// See the similarly named variant of `ResponseField`.
    Body,
    /// See the similarly named variant of `ResponseField`.
    Header,
    /// See the similarly named variant of `ResponseField`.
    NewtypeBody,
}
<|endoftext|>