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

//! # The Rust Standard Library
//!
//! The Rust Standard Library provides the essential runtime
//! functionality for building portable Rust software.
//! It is linked to all Rust crates by default.
//!
//! ## Intrinsic types and operations
//!
//! The [`ptr`](../core/ptr/index.html), [`mem`](../core/mem/index.html),
//! modules deal with unsafe pointers and memory manipulation.
//! [`kinds`](../core/kinds/index.html) defines the special built-in traits,
//! and [`raw`](../core/raw/index.html) the runtime representation of Rust types.
//! These are some of the lowest-level building blocks of Rust
//! abstractions.
//!
//! ## Math on primitive types and math traits
//!
//! Although basic operations on primitive types are implemented
//! directly by the compiler, the standard library additionally
//! defines many common operations through traits defined in
//! mod [`num`](num/index.html).
//!
//! ## Pervasive types
//!
//! The [`option`](option/index.html) and [`result`](result/index.html)
//! modules define optional and error-handling types, `Option` and `Result`.
//! [`iter`](../core/iter/index.html) defines Rust's iterator protocol
//! along with a wide variety of iterators.
//! [`Cell` and `RefCell`](../core/cell/index.html) are for creating types that
//! manage their own mutability.
//!
//! ## Vectors, slices and strings
//!
//! The common container type, `Vec`, a growable vector backed by an
//! array, lives in the [`vec`](vec/index.html) module. References to
//! arrays, `&[T]`, more commonly called "slices", are built-in types
//! for which the [`slice`](slice/index.html) module defines many
//! methods.
//!
//! UTF-8 strings, `~str` and `&str`, are built-in types, and the
//! standard library defines methods for them on a variety of traits
//! in the [`str`](str/index.html) module. Rust strings are immutable;
//! use the `StrBuf` type defined in [`strbuf`](strbuf/index.html)
//! for a mutable string builder.
//!
//! For converting to strings use the [`format!`](fmt/index.html)
//! macro, and for converting from strings use the
//! [`FromStr`](from_str/index.html) trait.
//!
//! ## Platform abstractions
//!
//! Besides basic data types, the standard library is largely concerned
//! with abstracting over differences in common platforms, most notably
//! Windows and Unix derivatives. The [`os`](os/index.html) module
//! provides a number of basic functions for interacting with the
//! operating environment, including program arguments, environment
//! variables, and directory navigation. The [`path`](path/index.html)
//! module encapsulates the platform-specific rules for dealing
//! with file paths.
//!
//! `std` also includes modules for interoperating with the
//! C language: [`c_str`](c_str/index.html) and
//! [`c_vec`](c_vec/index.html).
//!
//! ## Concurrency, I/O, and the runtime
//!
//! The [`task`](task/index.html) module contains Rust's threading abstractions,
//! while [`comm`](comm/index.html) contains the channel types for message
//! passing. [`sync`](sync/index.html) contains further, primitive, shared
//! memory types, including [`atomics`](sync/atomics/index.html).
//!
//! Common types of I/O, including files, TCP, UPD, pipes, Unix domain sockets,
//! timers, and process spawning, are defined in the [`io`](io/index.html) module.
//!
//! Rust's I/O and concurrency depends on a small runtime interface
//! that lives, along with its support code, in mod [`rt`](rt/index.html).
//! While a notable part of the standard library's architecture, this
//! module is not intended for public use.
//!
//! ## The Rust prelude and macros
//!
//! Finally, the [`prelude`](prelude/index.html) defines a
//! common set of traits, types, and functions that are made available
//! to all code by default. [`macros`](macros/index.html) contains
//! all the standard macros, such as `assert!`, `fail!`, `println!`,
//! and `format!`, also available to all Rust code.

#![crate_id = "std#0.11.0-pre"]
#![comment = "The Rust standard library"]
#![license = "MIT/ASL2"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
       html_root_url = "http://static.rust-lang.org/doc/master")]
#![feature(macro_rules, globs, asm, managed_boxes, thread_local, link_args,
           simd, linkage, default_type_params, phase, concat_idents, quad_precision_float)]

// Don't link to std. We are std.
#![no_std]

#![allow(deprecated)]
#![deny(missing_doc)]

// When testing libstd, bring in libuv as the I/O backend so tests can print
// things and all of the std::io tests have an I/O interface to run on top
// of
#[cfg(test)] extern crate rustuv;
#[cfg(test)] extern crate native;
#[cfg(test)] extern crate green;
#[cfg(test)] #[phase(syntax, link)] extern crate log;

// Make and rand accessible for benchmarking/testcases
#[cfg(test)] extern crate rand;

extern crate alloc;
extern crate core;
extern crate libc;

// Make std testable by not duplicating lang items. See #2912
#[cfg(test)] extern crate realstd = "std";
#[cfg(test)] pub use realstd::kinds;
#[cfg(test)] pub use realstd::ops;
#[cfg(test)] pub use realstd::cmp;
#[cfg(test)] pub use realstd::ty;

#[cfg(not(test))] pub use core::cmp;
#[cfg(not(test))] pub use core::kinds;
#[cfg(not(test))] pub use core::ops;
#[cfg(not(test))] pub use core::ty;

pub use core::any;
pub use core::bool;
pub use core::cell;
pub use core::char;
pub use core::clone;
pub use core::container;
pub use core::default;
pub use core::intrinsics;
pub use core::iter;
pub use core::mem;
pub use core::ptr;
pub use core::raw;
pub use core::tuple;
pub use core::result;

pub use alloc::owned;
pub use alloc::rc;

// Run tests with libgreen instead of libnative.
//
// FIXME: This egregiously hacks around starting the test runner in a different
//        threading mode than the default by reaching into the auto-generated
//        '__test' module.
#[cfg(test)] #[start]
fn start(argc: int, argv: **u8) -> int {
    green::start(argc, argv, rustuv::event_loop, __test::main)
}

/* Exported macros */

pub mod macros;
pub mod bitflags;

mod rtdeps;

/* The Prelude. */

pub mod prelude;

/* Primitive types */

#[path = "num/float_macros.rs"] mod float_macros;
#[path = "num/int_macros.rs"]   mod int_macros;
#[path = "num/uint_macros.rs"]  mod uint_macros;

#[path = "num/int.rs"]  pub mod int;
#[path = "num/i8.rs"]   pub mod i8;
#[path = "num/i16.rs"]  pub mod i16;
#[path = "num/i32.rs"]  pub mod i32;
#[path = "num/i64.rs"]  pub mod i64;

#[path = "num/uint.rs"] pub mod uint;
#[path = "num/u8.rs"]   pub mod u8;
#[path = "num/u16.rs"]  pub mod u16;
#[path = "num/u32.rs"]  pub mod u32;
#[path = "num/u64.rs"]  pub mod u64;

#[path = "num/f32.rs"]   pub mod f32;
#[path = "num/f64.rs"]   pub mod f64;

pub mod slice;
pub mod vec;
pub mod str;
pub mod strbuf;

pub mod ascii;

pub mod gc;

/* Common traits */

pub mod from_str;
pub mod num;
pub mod to_str;
pub mod hash;

/* Common data structures */

pub mod option;

/* Tasks and communication */

pub mod task;
pub mod comm;
pub mod local_data;
pub mod sync;

/* Runtime and platform support */

pub mod c_str;
pub mod c_vec;
pub mod os;
pub mod io;
pub mod path;
pub mod fmt;
pub mod cleanup;

/* Unsupported interfaces */

#[unstable]
pub mod repr;
#[unstable]
pub mod reflect;

// Private APIs
#[unstable]
pub mod unstable;

/* For internal use, not exported */

mod unicode;

// FIXME #7809: This shouldn't be pub, and it should be reexported under 'unstable'
// but name resolution doesn't work without it being pub.
#[unstable]
pub mod rt;

// A curious inner-module that's not exported that contains the binding
// 'std' so that macro-expanded references to std::error and such
// can be resolved within libstd.
#[doc(hidden)]
mod std {
    pub use clone;
    pub use cmp;
    pub use comm;
    pub use fmt;
    pub use hash;
    pub use io;
    pub use kinds;
    pub use local_data;
    pub use option;
    pub use os;
    pub use rt;
    pub use str;
    pub use to_str;
    pub use ty;
    pub use unstable;
    pub use vec;

    // The test runner requires std::slice::Vector, so re-export std::slice just for it.
    #[cfg(test)] pub use slice;
}
<commit_msg>std: Alphabetize crate reexports for rustdoc<commit_after>// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! # The Rust Standard Library
//!
//! The Rust Standard Library provides the essential runtime
//! functionality for building portable Rust software.
//! It is linked to all Rust crates by default.
//!
//! ## Intrinsic types and operations
//!
//! The [`ptr`](../core/ptr/index.html), [`mem`](../core/mem/index.html),
//! modules deal with unsafe pointers and memory manipulation.
//! [`kinds`](../core/kinds/index.html) defines the special built-in traits,
//! and [`raw`](../core/raw/index.html) the runtime representation of Rust types.
//! These are some of the lowest-level building blocks of Rust
//! abstractions.
//!
//! ## Math on primitive types and math traits
//!
//! Although basic operations on primitive types are implemented
//! directly by the compiler, the standard library additionally
//! defines many common operations through traits defined in
//! mod [`num`](num/index.html).
//!
//! ## Pervasive types
//!
//! The [`option`](option/index.html) and [`result`](result/index.html)
//! modules define optional and error-handling types, `Option` and `Result`.
//! [`iter`](../core/iter/index.html) defines Rust's iterator protocol
//! along with a wide variety of iterators.
//! [`Cell` and `RefCell`](../core/cell/index.html) are for creating types that
//! manage their own mutability.
//!
//! ## Vectors, slices and strings
//!
//! The common container type, `Vec`, a growable vector backed by an
//! array, lives in the [`vec`](vec/index.html) module. References to
//! arrays, `&[T]`, more commonly called "slices", are built-in types
//! for which the [`slice`](slice/index.html) module defines many
//! methods.
//!
//! UTF-8 strings, `~str` and `&str`, are built-in types, and the
//! standard library defines methods for them on a variety of traits
//! in the [`str`](str/index.html) module. Rust strings are immutable;
//! use the `StrBuf` type defined in [`strbuf`](strbuf/index.html)
//! for a mutable string builder.
//!
//! For converting to strings use the [`format!`](fmt/index.html)
//! macro, and for converting from strings use the
//! [`FromStr`](from_str/index.html) trait.
//!
//! ## Platform abstractions
//!
//! Besides basic data types, the standard library is largely concerned
//! with abstracting over differences in common platforms, most notably
//! Windows and Unix derivatives. The [`os`](os/index.html) module
//! provides a number of basic functions for interacting with the
//! operating environment, including program arguments, environment
//! variables, and directory navigation. The [`path`](path/index.html)
//! module encapsulates the platform-specific rules for dealing
//! with file paths.
//!
//! `std` also includes modules for interoperating with the
//! C language: [`c_str`](c_str/index.html) and
//! [`c_vec`](c_vec/index.html).
//!
//! ## Concurrency, I/O, and the runtime
//!
//! The [`task`](task/index.html) module contains Rust's threading abstractions,
//! while [`comm`](comm/index.html) contains the channel types for message
//! passing. [`sync`](sync/index.html) contains further, primitive, shared
//! memory types, including [`atomics`](sync/atomics/index.html).
//!
//! Common types of I/O, including files, TCP, UPD, pipes, Unix domain sockets,
//! timers, and process spawning, are defined in the [`io`](io/index.html) module.
//!
//! Rust's I/O and concurrency depends on a small runtime interface
//! that lives, along with its support code, in mod [`rt`](rt/index.html).
//! While a notable part of the standard library's architecture, this
//! module is not intended for public use.
//!
//! ## The Rust prelude and macros
//!
//! Finally, the [`prelude`](prelude/index.html) defines a
//! common set of traits, types, and functions that are made available
//! to all code by default. [`macros`](macros/index.html) contains
//! all the standard macros, such as `assert!`, `fail!`, `println!`,
//! and `format!`, also available to all Rust code.

#![crate_id = "std#0.11.0-pre"]
#![comment = "The Rust standard library"]
#![license = "MIT/ASL2"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
       html_root_url = "http://static.rust-lang.org/doc/master")]
#![feature(macro_rules, globs, asm, managed_boxes, thread_local, link_args,
           simd, linkage, default_type_params, phase, concat_idents, quad_precision_float)]

// Don't link to std. We are std.
#![no_std]

#![allow(deprecated)]
#![deny(missing_doc)]

// When testing libstd, bring in libuv as the I/O backend so tests can print
// things and all of the std::io tests have an I/O interface to run on top
// of
#[cfg(test)] extern crate rustuv;
#[cfg(test)] extern crate native;
#[cfg(test)] extern crate green;
#[cfg(test)] #[phase(syntax, link)] extern crate log;

// Make and rand accessible for benchmarking/testcases
#[cfg(test)] extern crate rand;

extern crate alloc;
extern crate core;
extern crate libc;

// Make std testable by not duplicating lang items. See #2912
#[cfg(test)] extern crate realstd = "std";
#[cfg(test)] pub use realstd::kinds;
#[cfg(test)] pub use realstd::ops;
#[cfg(test)] pub use realstd::cmp;
#[cfg(test)] pub use realstd::ty;

// NB: These reexports are in the order they should be listed in rustdoc

pub use core::any;
pub use core::bool;
pub use core::cell;
pub use core::char;
pub use core::clone;
#[cfg(not(test))] pub use core::cmp;
pub use core::container;
pub use core::default;
pub use core::intrinsics;
pub use core::iter;
#[cfg(not(test))] pub use core::kinds;
pub use core::mem;
#[cfg(not(test))] pub use core::ops;
pub use core::ptr;
pub use core::raw;
pub use core::tuple;
#[cfg(not(test))] pub use core::ty;
pub use core::result;

pub use alloc::owned;
pub use alloc::rc;

// Run tests with libgreen instead of libnative.
//
// FIXME: This egregiously hacks around starting the test runner in a different
//        threading mode than the default by reaching into the auto-generated
//        '__test' module.
#[cfg(test)] #[start]
fn start(argc: int, argv: **u8) -> int {
    green::start(argc, argv, rustuv::event_loop, __test::main)
}

/* Exported macros */

pub mod macros;
pub mod bitflags;

mod rtdeps;

/* The Prelude. */

pub mod prelude;

/* Primitive types */

#[path = "num/float_macros.rs"] mod float_macros;
#[path = "num/int_macros.rs"]   mod int_macros;
#[path = "num/uint_macros.rs"]  mod uint_macros;

#[path = "num/int.rs"]  pub mod int;
#[path = "num/i8.rs"]   pub mod i8;
#[path = "num/i16.rs"]  pub mod i16;
#[path = "num/i32.rs"]  pub mod i32;
#[path = "num/i64.rs"]  pub mod i64;

#[path = "num/uint.rs"] pub mod uint;
#[path = "num/u8.rs"]   pub mod u8;
#[path = "num/u16.rs"]  pub mod u16;
#[path = "num/u32.rs"]  pub mod u32;
#[path = "num/u64.rs"]  pub mod u64;

#[path = "num/f32.rs"]   pub mod f32;
#[path = "num/f64.rs"]   pub mod f64;

pub mod slice;
pub mod vec;
pub mod str;
pub mod strbuf;

pub mod ascii;

pub mod gc;

/* Common traits */

pub mod from_str;
pub mod num;
pub mod to_str;
pub mod hash;

/* Common data structures */

pub mod option;

/* Tasks and communication */

pub mod task;
pub mod comm;
pub mod local_data;
pub mod sync;

/* Runtime and platform support */

pub mod c_str;
pub mod c_vec;
pub mod os;
pub mod io;
pub mod path;
pub mod fmt;
pub mod cleanup;

/* Unsupported interfaces */

#[unstable]
pub mod repr;
#[unstable]
pub mod reflect;

// Private APIs
#[unstable]
pub mod unstable;

/* For internal use, not exported */

mod unicode;

// FIXME #7809: This shouldn't be pub, and it should be reexported under 'unstable'
// but name resolution doesn't work without it being pub.
#[unstable]
pub mod rt;

// A curious inner-module that's not exported that contains the binding
// 'std' so that macro-expanded references to std::error and such
// can be resolved within libstd.
#[doc(hidden)]
mod std {
    pub use clone;
    pub use cmp;
    pub use comm;
    pub use fmt;
    pub use hash;
    pub use io;
    pub use kinds;
    pub use local_data;
    pub use option;
    pub use os;
    pub use rt;
    pub use str;
    pub use to_str;
    pub use ty;
    pub use unstable;
    pub use vec;

    // The test runner requires std::slice::Vector, so re-export std::slice just for it.
    #[cfg(test)] pub use slice;
}
<|endoftext|>


<commit_before>use crate::syntax::attrs::OtherAttrs;
use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::{Api, Discriminant, Doc, Enum, ForeignName, Pair, Variant};
use proc_macro2::{Delimiter, Group, Ident, TokenStream};
use quote::{format_ident, quote};
use serde::Deserialize;
use std::env;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;

const CXX_CLANG_AST: &str = "CXX_CLANG_AST";

type Node = clang_ast::Node<Clang>;

#[derive(Deserialize)]
enum Clang {
    NamespaceDecl(NamespaceDecl),
    EnumDecl(EnumDecl),
    EnumConstantDecl(EnumConstantDecl),
    ImplicitCastExpr,
    ConstantExpr(ConstantExpr),
    Unknown,
}

#[derive(Deserialize)]
struct NamespaceDecl {
    name: Option<String>,
}

#[derive(Deserialize)]
struct EnumDecl {
    name: Option<String>,
}

#[derive(Deserialize)]
struct EnumConstantDecl {
    name: String,
}

#[derive(Deserialize)]
struct ConstantExpr {
    value: String,
}

pub fn load(cx: &mut Errors, apis: &mut [Api]) {
    let ref mut variants_from_header = Vec::new();
    for api in apis {
        if let Api::Enum(enm) = api {
            if enm.variants_from_header {
                if enm.variants.is_empty() {
                    variants_from_header.push(enm);
                } else {
                    let span = span_for_enum_error(enm);
                    cx.error(
                        span,
                        "enum with #![variants_from_header] must be written with no explicit variants",
                    );
                }
            }
        }
    }

    let span = match variants_from_header.get(0) {
        None => return,
        Some(enm) => enm.variants_from_header_attr.clone().unwrap(),
    };

    let ast_dump_path = match env::var_os(CXX_CLANG_AST) {
        Some(ast_dump_path) => PathBuf::from(ast_dump_path),
        None => {
            let msg = format!(
                "environment variable ${} has not been provided",
                CXX_CLANG_AST,
            );
            return cx.error(span, msg);
        }
    };

    let ast_dump_bytes = match fs::read(&ast_dump_path) {
        Ok(ast_dump_bytes) => ast_dump_bytes,
        Err(error) => {
            let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
            return cx.error(span, msg);
        }
    };

    let ref root: Node = match serde_json::from_slice(&ast_dump_bytes) {
        Ok(root) => root,
        Err(error) => {
            let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
            return cx.error(span, msg);
        }
    };

    let ref mut namespace = Vec::new();
    traverse(cx, root, namespace, variants_from_header, None);

    for enm in variants_from_header {
        if enm.variants.is_empty() {
            let span = &enm.variants_from_header_attr;
            let mut msg = "failed to find any C++ definition of enum ".to_owned();
            for name in &enm.name.namespace {
                msg += &name.to_string();
                msg += "::";
            }
            msg += &enm.name.cxx.to_string();
            cx.error(span, msg);
        }
    }
}

fn traverse<'a>(
    cx: &mut Errors,
    node: &'a Node,
    namespace: &mut Vec<&'a str>,
    variants_from_header: &mut [&mut Enum],
    mut idx: Option<usize>,
) {
    match &node.kind {
        Clang::NamespaceDecl(decl) => {
            let name = match &decl.name {
                Some(name) => name,
                // Can ignore enums inside an anonymous namespace.
                None => return,
            };
            namespace.push(name);
            idx = None;
        }
        Clang::EnumDecl(decl) => {
            let name = match &decl.name {
                Some(name) => name,
                None => return,
            };
            idx = None;
            for (i, enm) in variants_from_header.iter().enumerate() {
                if enm.name.cxx == **name && enm.name.namespace.iter().eq(&*namespace) {
                    idx = Some(i);
                    break;
                }
            }
            if idx.is_none() {
                return;
            }
        }
        Clang::EnumConstantDecl(decl) => {
            if let Some(idx) = idx {
                let enm = &mut *variants_from_header[idx];
                let span = enm
                    .variants_from_header_attr
                    .as_ref()
                    .unwrap()
                    .path
                    .get_ident()
                    .unwrap()
                    .span();
                let cxx_name = match ForeignName::parse(&decl.name, span) {
                    Ok(foreign_name) => foreign_name,
                    Err(_) => {
                        let span = &enm.variants_from_header_attr;
                        let msg = format!("unsupported C++ variant name: {}", decl.name);
                        return cx.error(span, msg);
                    }
                };
                let rust_name: Ident = match syn::parse_str(&decl.name) {
                    Ok(ident) => ident,
                    Err(_) => format_ident!("__Variant{}", enm.variants.len()),
                };
                let discriminant = match discriminant_value(&node.inner) {
                    ParsedDiscriminant::Constant(discriminant) => discriminant,
                    ParsedDiscriminant::Successor => match enm.variants.last() {
                        None => Discriminant::zero(),
                        Some(last) => match last.discriminant.checked_succ() {
                            Some(discriminant) => discriminant,
                            None => {
                                let span = &enm.variants_from_header_attr;
                                let msg = format!(
                                    "overflow processing discriminant value for variant: {}",
                                    decl.name,
                                );
                                return cx.error(span, msg);
                            }
                        },
                    },
                    ParsedDiscriminant::Fail => {
                        let span = &enm.variants_from_header_attr;
                        let msg = format!(
                            "failed to obtain discriminant value for variant: {}",
                            decl.name,
                        );
                        cx.error(span, msg);
                        Discriminant::zero()
                    }
                };
                enm.variants.push(Variant {
                    doc: Doc::new(),
                    attrs: OtherAttrs::none(),
                    name: Pair {
                        namespace: Namespace::ROOT,
                        cxx: cxx_name,
                        rust: rust_name,
                    },
                    discriminant,
                    expr: None,
                });
            }
        }
        _ => {}
    }
    for inner in &node.inner {
        traverse(cx, inner, namespace, variants_from_header, idx);
    }
    if let Clang::NamespaceDecl(_) = &node.kind {
        let _ = namespace.pop().unwrap();
    }
}

enum ParsedDiscriminant {
    Constant(Discriminant),
    Successor,
    Fail,
}

fn discriminant_value(mut clang: &[Node]) -> ParsedDiscriminant {
    if clang.is_empty() {
        // No discriminant expression provided; use successor of previous
        // descriminant.
        return ParsedDiscriminant::Successor;
    }

    loop {
        if clang.len() != 1 {
            return ParsedDiscriminant::Fail;
        }

        let node = &clang[0];
        match &node.kind {
            Clang::ImplicitCastExpr => clang = &node.inner,
            Clang::ConstantExpr(expr) => match Discriminant::from_str(&expr.value) {
                Ok(discriminant) => return ParsedDiscriminant::Constant(discriminant),
                Err(_) => return ParsedDiscriminant::Fail,
            },
            _ => return ParsedDiscriminant::Fail,
        }
    }
}

fn span_for_enum_error(enm: &Enum) -> TokenStream {
    let enum_token = enm.enum_token;
    let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
    brace_token.set_span(enm.brace_token.span);
    quote!(#enum_token #brace_token)
}
<commit_msg>Raise error on repeated definitions of same enum<commit_after>use crate::syntax::attrs::OtherAttrs;
use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::{Api, Discriminant, Doc, Enum, ForeignName, Pair, Variant};
use proc_macro2::{Delimiter, Group, Ident, TokenStream};
use quote::{format_ident, quote};
use serde::Deserialize;
use std::env;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;

const CXX_CLANG_AST: &str = "CXX_CLANG_AST";

type Node = clang_ast::Node<Clang>;

#[derive(Deserialize)]
enum Clang {
    NamespaceDecl(NamespaceDecl),
    EnumDecl(EnumDecl),
    EnumConstantDecl(EnumConstantDecl),
    ImplicitCastExpr,
    ConstantExpr(ConstantExpr),
    Unknown,
}

#[derive(Deserialize)]
struct NamespaceDecl {
    name: Option<String>,
}

#[derive(Deserialize)]
struct EnumDecl {
    name: Option<String>,
}

#[derive(Deserialize)]
struct EnumConstantDecl {
    name: String,
}

#[derive(Deserialize)]
struct ConstantExpr {
    value: String,
}

pub fn load(cx: &mut Errors, apis: &mut [Api]) {
    let ref mut variants_from_header = Vec::new();
    for api in apis {
        if let Api::Enum(enm) = api {
            if enm.variants_from_header {
                if enm.variants.is_empty() {
                    variants_from_header.push(enm);
                } else {
                    let span = span_for_enum_error(enm);
                    cx.error(
                        span,
                        "enum with #![variants_from_header] must be written with no explicit variants",
                    );
                }
            }
        }
    }

    let span = match variants_from_header.get(0) {
        None => return,
        Some(enm) => enm.variants_from_header_attr.clone().unwrap(),
    };

    let ast_dump_path = match env::var_os(CXX_CLANG_AST) {
        Some(ast_dump_path) => PathBuf::from(ast_dump_path),
        None => {
            let msg = format!(
                "environment variable ${} has not been provided",
                CXX_CLANG_AST,
            );
            return cx.error(span, msg);
        }
    };

    let ast_dump_bytes = match fs::read(&ast_dump_path) {
        Ok(ast_dump_bytes) => ast_dump_bytes,
        Err(error) => {
            let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
            return cx.error(span, msg);
        }
    };

    let ref root: Node = match serde_json::from_slice(&ast_dump_bytes) {
        Ok(root) => root,
        Err(error) => {
            let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
            return cx.error(span, msg);
        }
    };

    let ref mut namespace = Vec::new();
    traverse(cx, root, namespace, variants_from_header, None);

    for enm in variants_from_header {
        if enm.variants.is_empty() {
            let span = &enm.variants_from_header_attr;
            let mut msg = "failed to find any C++ definition of enum ".to_owned();
            for name in &enm.name.namespace {
                msg += &name.to_string();
                msg += "::";
            }
            msg += &enm.name.cxx.to_string();
            cx.error(span, msg);
        }
    }
}

fn traverse<'a>(
    cx: &mut Errors,
    node: &'a Node,
    namespace: &mut Vec<&'a str>,
    variants_from_header: &mut [&mut Enum],
    mut idx: Option<usize>,
) {
    match &node.kind {
        Clang::NamespaceDecl(decl) => {
            let name = match &decl.name {
                Some(name) => name,
                // Can ignore enums inside an anonymous namespace.
                None => return,
            };
            namespace.push(name);
            idx = None;
        }
        Clang::EnumDecl(decl) => {
            let name = match &decl.name {
                Some(name) => name,
                None => return,
            };
            idx = None;
            for (i, enm) in variants_from_header.iter().enumerate() {
                if enm.name.cxx == **name && enm.name.namespace.iter().eq(&*namespace) {
                    if enm.variants.is_empty() {
                        idx = Some(i);
                        break;
                    } else {
                        let span = &enm.variants_from_header_attr;
                        let mut msg = "found multiple C++ definitions of enum ".to_owned();
                        for name in &enm.name.namespace {
                            msg += &name.to_string();
                            msg += "::";
                        }
                        msg += &enm.name.cxx.to_string();
                        cx.error(span, msg);
                        return;
                    }
                }
            }
            if idx.is_none() {
                return;
            }
        }
        Clang::EnumConstantDecl(decl) => {
            if let Some(idx) = idx {
                let enm = &mut *variants_from_header[idx];
                let span = enm
                    .variants_from_header_attr
                    .as_ref()
                    .unwrap()
                    .path
                    .get_ident()
                    .unwrap()
                    .span();
                let cxx_name = match ForeignName::parse(&decl.name, span) {
                    Ok(foreign_name) => foreign_name,
                    Err(_) => {
                        let span = &enm.variants_from_header_attr;
                        let msg = format!("unsupported C++ variant name: {}", decl.name);
                        return cx.error(span, msg);
                    }
                };
                let rust_name: Ident = match syn::parse_str(&decl.name) {
                    Ok(ident) => ident,
                    Err(_) => format_ident!("__Variant{}", enm.variants.len()),
                };
                let discriminant = match discriminant_value(&node.inner) {
                    ParsedDiscriminant::Constant(discriminant) => discriminant,
                    ParsedDiscriminant::Successor => match enm.variants.last() {
                        None => Discriminant::zero(),
                        Some(last) => match last.discriminant.checked_succ() {
                            Some(discriminant) => discriminant,
                            None => {
                                let span = &enm.variants_from_header_attr;
                                let msg = format!(
                                    "overflow processing discriminant value for variant: {}",
                                    decl.name,
                                );
                                return cx.error(span, msg);
                            }
                        },
                    },
                    ParsedDiscriminant::Fail => {
                        let span = &enm.variants_from_header_attr;
                        let msg = format!(
                            "failed to obtain discriminant value for variant: {}",
                            decl.name,
                        );
                        cx.error(span, msg);
                        Discriminant::zero()
                    }
                };
                enm.variants.push(Variant {
                    doc: Doc::new(),
                    attrs: OtherAttrs::none(),
                    name: Pair {
                        namespace: Namespace::ROOT,
                        cxx: cxx_name,
                        rust: rust_name,
                    },
                    discriminant,
                    expr: None,
                });
            }
        }
        _ => {}
    }
    for inner in &node.inner {
        traverse(cx, inner, namespace, variants_from_header, idx);
    }
    if let Clang::NamespaceDecl(_) = &node.kind {
        let _ = namespace.pop().unwrap();
    }
}

enum ParsedDiscriminant {
    Constant(Discriminant),
    Successor,
    Fail,
}

fn discriminant_value(mut clang: &[Node]) -> ParsedDiscriminant {
    if clang.is_empty() {
        // No discriminant expression provided; use successor of previous
        // descriminant.
        return ParsedDiscriminant::Successor;
    }

    loop {
        if clang.len() != 1 {
            return ParsedDiscriminant::Fail;
        }

        let node = &clang[0];
        match &node.kind {
            Clang::ImplicitCastExpr => clang = &node.inner,
            Clang::ConstantExpr(expr) => match Discriminant::from_str(&expr.value) {
                Ok(discriminant) => return ParsedDiscriminant::Constant(discriminant),
                Err(_) => return ParsedDiscriminant::Fail,
            },
            _ => return ParsedDiscriminant::Fail,
        }
    }
}

fn span_for_enum_error(enm: &Enum) -> TokenStream {
    let enum_token = enm.enum_token;
    let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
    brace_token.set_span(enm.brace_token.span);
    quote!(#enum_token #brace_token)
}
<|endoftext|>


<commit_before>//! Manager that is called when an output is created or destroyed.
//! Pass a struct that implements this trait to the `Compositor` during
//! initialization.

use compositor::{Compositor, COMPOSITOR_PTR};
use libc;
use manager::{OutputHandler, UserOutput};
use types::Output;

use wayland_sys::server::WAYLAND_SERVER_HANDLE;
use wayland_sys::server::signal::wl_signal_add;
use wlroots_sys::wlr_output;

/// Used to ensure the output sets the mode before doing any other
/// operation on the Output.
pub struct OutputBuilder<'output> {
    output: &'output mut Output
}

/// Used to ensure that the builder is used to construct
/// the OutputHandler instance.
pub struct OutputBuilderResult<'output> {
    pub output: &'output mut Output,
    result: Box<OutputHandler>
}

/// Wrapper around Output destruction so that you can't call
/// unsafe methods (e.g anything like setting the mode).
pub struct OutputDestruction<'output>(&'output mut Output);

/// Handles output addition and removal.
pub trait OutputManagerHandler {
    /// Called whenever an output is added.
    fn output_added<'output>(&mut self,
                             &mut Compositor,
                             _: OutputBuilder<'output>)
                             -> Option<OutputBuilderResult<'output>> {
        None
    }

    /// Called whenever an output is removed.
    fn output_removed(&mut self, &mut Compositor, OutputDestruction) {
        // TODO
    }
}

impl<'output> OutputBuilder<'output> {
    pub fn build_best_mode<T: OutputHandler + 'static>(self,
                                                       data: T)
                                                       -> OutputBuilderResult<'output> {
        self.output.choose_best_mode();
        OutputBuilderResult { output: self.output,
                              result: Box::new(data) }
    }
}

impl<'output> OutputDestruction<'output> {
    // TODO Functions which are safe to use
}

wayland_listener!(OutputManager, (Vec<Box<UserOutput>>, Box<OutputManagerHandler>), [
    add_listener => add_notify: |this: &mut OutputManager, data: *mut libc::c_void,| unsafe {
        let (ref mut outputs, ref mut manager) = this.data;
        let data = data as *mut wlr_output;
        let mut output = Output::new(data as *mut wlr_output);
        // NOTE
        // This clone is required because we pass it mutably to the output builder,
        // but due to lack of NLL there's no way to tell Rust it's safe to use it in
        // in the if branch.
        //
        // Thus, we need to clone it here and then drop the original once at the end.
        //
        // This is not a real clone, but an pub(crate) unsafe one we added, so it doesn't
        // break safety concerns in user code. Just an unfortunate hack we have to put here.
        let output_clone = output.clone();
        let builder = OutputBuilder { output: &mut output };
        let compositor = &mut *COMPOSITOR_PTR;
        let build_result = manager.output_added(compositor, builder);
        if let Some(OutputBuilderResult {result: output_ptr, .. }) = build_result {
            let mut output = UserOutput::new((output_clone, output_ptr));
            // Add the output frame event to this manager
            wl_signal_add(&mut (*data).events.frame as *mut _ as _,
                        output.frame_listener() as _);
            // Add the output resolution event to this manager
            wl_signal_add(&mut (*data).events.resolution as *mut _ as _,
                        output.resolution_listener() as _);
            // Store the user UserOutput, free later in remove listener
            outputs.push(output);
        }
    };
    remove_listener => remove_notify: |this: &mut OutputManager, data: *mut libc::c_void,| unsafe {
        let (ref mut outputs, ref mut manager) = this.data;
        let data = data as *mut wlr_output;
        // NOTE
        // We get it from the list so that we can get the Rc'd `Output`, because there's
        // no way to re-construct that using just the raw pointer.
        {
            let output = &mut outputs.iter_mut().find(|output| output.output_ptr() == data)
                // NOTE
                // Rationale for panicking: Will be caught by panic catchers.
                .expect("remove_listener got an output that it doesn't control!")
                .output_mut();
            let compositor = &mut *COMPOSITOR_PTR;
            manager.output_removed(compositor, OutputDestruction(output));
            if let Some(layout) = output.layout() {
                layout.borrow_mut().remove(output);
            }
        }
        // Remove user output data
        if let Some(index) = outputs.iter().position(|output| output.output_ptr() == data) {
            let mut removed_output = outputs.remove(index);
            ffi_dispatch!(WAYLAND_SERVER_HANDLE,
                          wl_list_remove,
                          &mut (*removed_output.frame_listener()).link as *mut _ as _);
            ffi_dispatch!(WAYLAND_SERVER_HANDLE,
                          wl_list_remove,
                          &mut (*removed_output.resolution_listener()).link as *mut _ as _);

        }
    };
]);
<commit_msg>Don't panic we find an output that's not ours<commit_after>//! Manager that is called when an output is created or destroyed.
//! Pass a struct that implements this trait to the `Compositor` during
//! initialization.

use compositor::{Compositor, COMPOSITOR_PTR};
use libc;
use manager::{OutputHandler, UserOutput};
use types::Output;

use wayland_sys::server::WAYLAND_SERVER_HANDLE;
use wayland_sys::server::signal::wl_signal_add;
use wlroots_sys::wlr_output;

/// Used to ensure the output sets the mode before doing any other
/// operation on the Output.
pub struct OutputBuilder<'output> {
    output: &'output mut Output
}

/// Used to ensure that the builder is used to construct
/// the OutputHandler instance.
pub struct OutputBuilderResult<'output> {
    pub output: &'output mut Output,
    result: Box<OutputHandler>
}

/// Wrapper around Output destruction so that you can't call
/// unsafe methods (e.g anything like setting the mode).
pub struct OutputDestruction<'output>(&'output mut Output);

/// Handles output addition and removal.
pub trait OutputManagerHandler {
    /// Called whenever an output is added.
    fn output_added<'output>(&mut self,
                             &mut Compositor,
                             _: OutputBuilder<'output>)
                             -> Option<OutputBuilderResult<'output>> {
        None
    }

    /// Called whenever an output is removed.
    fn output_removed(&mut self, &mut Compositor, OutputDestruction) {
        // TODO
    }
}

impl<'output> OutputBuilder<'output> {
    pub fn build_best_mode<T: OutputHandler + 'static>(self,
                                                       data: T)
                                                       -> OutputBuilderResult<'output> {
        self.output.choose_best_mode();
        OutputBuilderResult { output: self.output,
                              result: Box::new(data) }
    }
}

impl<'output> OutputDestruction<'output> {
    // TODO Functions which are safe to use
}

wayland_listener!(OutputManager, (Vec<Box<UserOutput>>, Box<OutputManagerHandler>), [
    add_listener => add_notify: |this: &mut OutputManager, data: *mut libc::c_void,| unsafe {
        let (ref mut outputs, ref mut manager) = this.data;
        let data = data as *mut wlr_output;
        let mut output = Output::new(data as *mut wlr_output);
        // NOTE
        // This clone is required because we pass it mutably to the output builder,
        // but due to lack of NLL there's no way to tell Rust it's safe to use it in
        // in the if branch.
        //
        // Thus, we need to clone it here and then drop the original once at the end.
        //
        // This is not a real clone, but an pub(crate) unsafe one we added, so it doesn't
        // break safety concerns in user code. Just an unfortunate hack we have to put here.
        let output_clone = output.clone();
        let builder = OutputBuilder { output: &mut output };
        let compositor = &mut *COMPOSITOR_PTR;
        let build_result = manager.output_added(compositor, builder);
        if let Some(OutputBuilderResult {result: output_ptr, .. }) = build_result {
            let mut output = UserOutput::new((output_clone, output_ptr));
            // Add the output frame event to this manager
            wl_signal_add(&mut (*data).events.frame as *mut _ as _,
                        output.frame_listener() as _);
            // Add the output resolution event to this manager
            wl_signal_add(&mut (*data).events.resolution as *mut _ as _,
                        output.resolution_listener() as _);
            // Store the user UserOutput, free later in remove listener
            outputs.push(output);
        }
    };
    remove_listener => remove_notify: |this: &mut OutputManager, data: *mut libc::c_void,| unsafe {
        let (ref mut outputs, ref mut manager) = this.data;
        let data = data as *mut wlr_output;
        // NOTE
        // We get it from the list so that we can get the Rc'd `Output`, because there's
        // no way to re-construct that using just the raw pointer.
        if let Some(output) = outputs.iter_mut().find(|output| output.output_ptr() == data) {
            let output = output.output_mut();
            let compositor = &mut *COMPOSITOR_PTR;
            manager.output_removed(compositor, OutputDestruction(output));
            if let Some(layout) = output.layout() {
                layout.borrow_mut().remove(output);
            }
        }
        // Remove user output data
        if let Some(index) = outputs.iter().position(|output| output.output_ptr() == data) {
            let mut removed_output = outputs.remove(index);
            ffi_dispatch!(WAYLAND_SERVER_HANDLE,
                          wl_list_remove,
                          &mut (*removed_output.frame_listener()).link as *mut _ as _);
            ffi_dispatch!(WAYLAND_SERVER_HANDLE,
                          wl_list_remove,
                          &mut (*removed_output.resolution_listener()).link as *mut _ as _);

        }
    };
]);
<|endoftext|>


<commit_before>use std::{
    fs::{self, File},
    path::{Path, PathBuf},
};

use self::configuration::VisibilityConfig;
use crate::{
    background_jobs::Environment,
    uploaders::{UploadBucket, Uploader},
};
use reqwest::header;
use swirl::PerformError;

/// Create CSV dumps of the public information in the database, wrap them in a
/// tarball and upload to S3.
#[swirl::background_job]
pub fn dump_db(
    env: &Environment,
    database_url: String,
    target_name: String,
) -> Result<(), PerformError> {
    let directory = DumpDirectory::create()?;

    println!("Begin exporting database");
    directory.populate(&database_url)?;

    println!("Creating tarball");
    let tarball = DumpTarball::create(&directory.export_dir)?;

    println!("Uploading tarball");
    let size = tarball.upload(&target_name, &env.uploader)?;
    println!("Database dump uploaded {} bytes to {}.", size, &target_name);
    Ok(())
}

/// Manage the export directory.
///
/// Create the directory, populate it with the psql scripts and CSV dumps, and
/// make sure it gets deleted again even in the case of an error.
#[derive(Debug)]
pub struct DumpDirectory {
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub export_dir: PathBuf,
}

impl DumpDirectory {
    pub fn create() -> Result<Self, PerformError> {
        let timestamp = chrono::Utc::now();
        let timestamp_str = timestamp.format("%Y-%m-%d-%H%M%S").to_string();
        let export_dir = std::env::temp_dir().join("dump-db").join(timestamp_str);
        debug!(?export_dir, "Creating database dump folder…");
        std::fs::create_dir_all(&export_dir)?;
        Ok(Self {
            timestamp,
            export_dir,
        })
    }

    pub fn populate(&self, database_url: &str) -> Result<(), PerformError> {
        self.add_readme()?;
        self.add_metadata()?;
        self.dump_schema(database_url)?;
        self.dump_db(database_url)
    }

    fn add_readme(&self) -> Result<(), PerformError> {
        use std::io::Write;

        let path = self.export_dir.join("README.md");
        debug!(?path, "Writing README.md file…");
        let mut readme = File::create(path)?;
        readme.write_all(include_bytes!("dump_db/readme_for_tarball.md"))?;
        Ok(())
    }

    fn add_metadata(&self) -> Result<(), PerformError> {
        #[derive(Serialize)]
        struct Metadata<'a> {
            timestamp: &'a chrono::DateTime<chrono::Utc>,
            crates_io_commit: String,
        }
        let metadata = Metadata {
            timestamp: &self.timestamp,
            crates_io_commit: dotenv::var("HEROKU_SLUG_COMMIT")
                .unwrap_or_else(|_| "unknown".to_owned()),
        };
        let path = self.export_dir.join("metadata.json");
        debug!(?path, "Writing metadata.json file…");
        let file = File::create(path)?;
        serde_json::to_writer_pretty(file, &metadata)?;
        Ok(())
    }

    pub fn dump_schema(&self, database_url: &str) -> Result<(), PerformError> {
        let path = self.export_dir.join("schema.sql");
        debug!(?path, "Writing schema.sql file…");
        let schema_sql = File::create(path)?;

        let status = std::process::Command::new("pg_dump")
            .arg("--schema-only")
            .arg("--no-owner")
            .arg("--no-acl")
            .arg(database_url)
            .stdout(schema_sql)
            .spawn()?
            .wait()?;
        if !status.success() {
            return Err("pg_dump did not finish successfully.".into());
        }
        Ok(())
    }

    pub fn dump_db(&self, database_url: &str) -> Result<(), PerformError> {
        debug!("Generating export.sql and import.sql files…");
        let export_script = self.export_dir.join("export.sql");
        let import_script = self.export_dir.join("import.sql");
        gen_scripts::gen_scripts(&export_script, &import_script)?;

        debug!("Filling data folder…");
        std::fs::create_dir(self.export_dir.join("data"))?;
        run_psql(&export_script, database_url)
    }
}

impl Drop for DumpDirectory {
    fn drop(&mut self) {
        std::fs::remove_dir_all(&self.export_dir).unwrap();
    }
}

pub fn run_psql(script: &Path, database_url: &str) -> Result<(), PerformError> {
    debug!(?script, "Running psql script…");
    let psql_script = File::open(script)?;
    let psql = std::process::Command::new("psql")
        .arg(database_url)
        .current_dir(script.parent().unwrap())
        .stdin(psql_script)
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::piped())
        .spawn()?;
    let output = psql.wait_with_output()?;
    let stderr = String::from_utf8_lossy(&output.stderr);
    if stderr.contains("ERROR") {
        return Err(format!("Error while executing psql: {stderr}").into());
    }
    if !output.status.success() {
        return Err("psql did not finish successfully.".into());
    }
    Ok(())
}

/// Manage the tarball of the database dump.
///
/// Create the tarball, upload it to S3, and make sure it gets deleted.
struct DumpTarball {
    tarball_path: PathBuf,
}

impl DumpTarball {
    fn create(export_dir: &Path) -> Result<Self, PerformError> {
        let tarball_path = export_dir.with_extension("tar.gz");
        let tarfile = File::create(&tarball_path)?;
        let result = Self { tarball_path };
        let encoder = flate2::write::GzEncoder::new(tarfile, flate2::Compression::default());

        let mut archive = tar::Builder::new(encoder);
        let tar_top_dir = PathBuf::from(export_dir.file_name().unwrap());
        archive.append_dir(&tar_top_dir, export_dir)?;

        // Append readme, metadata, schemas.
        for entry in fs::read_dir(export_dir)? {
            let entry = entry?;
            let file_type = entry.file_type()?;
            if file_type.is_file() {
                let name_in_tar = tar_top_dir.join(entry.file_name());
                archive.append_path_with_name(entry.path(), name_in_tar)?;
            }
        }

        // Append topologically sorted tables to make it possible to pipeline
        // importing with gz extraction.
        archive.append_dir(tar_top_dir.join("data"), export_dir.join("data"))?;
        for table in VisibilityConfig::get().topological_sort() {
            let csv_path = export_dir.join("data").join(table).with_extension("csv");
            if csv_path.exists() {
                let name_in_tar = tar_top_dir.join("data").join(table).with_extension("csv");
                archive.append_path_with_name(csv_path, name_in_tar)?;
            }
        }

        Ok(result)
    }

    fn upload(&self, target_name: &str, uploader: &Uploader) -> Result<u64, PerformError> {
        let client = reqwest::blocking::Client::new();
        let tarfile = File::open(&self.tarball_path)?;
        let content_length = tarfile.metadata()?.len();
        // TODO Figure out the correct content type.
        uploader.upload(
            &client,
            target_name,
            tarfile,
            "application/gzip",
            header::HeaderMap::new(),
            UploadBucket::Default,
        )?;
        Ok(content_length)
    }
}

impl Drop for DumpTarball {
    fn drop(&mut self) {
        std::fs::remove_file(&self.tarball_path).unwrap();
    }
}

mod configuration;
mod gen_scripts;

#[cfg(test)]
mod tests {
    use super::*;
    use flate2::read::GzDecoder;
    use tar::Archive;

    #[test]
    fn test_dump_tarball() {
        let tempdir = tempfile::Builder::new()
            .prefix("DumpTarball")
            .tempdir()
            .unwrap();
        let p = tempdir.path().join("0000-00-00");

        fs::create_dir(&p).unwrap();
        fs::write(p.join("README.md"), "# crates.io Database Dump\n").unwrap();
        fs::create_dir(p.join("data")).unwrap();
        fs::write(p.join("data").join("crates.csv"), "").unwrap();
        fs::write(p.join("data").join("crate_owners.csv"), "").unwrap();
        fs::write(p.join("data").join("users.csv"), "").unwrap();

        let tarball = DumpTarball::create(&p).unwrap();
        let gz = GzDecoder::new(File::open(&*tarball.tarball_path).unwrap());
        let mut tar = Archive::new(gz);

        for (i, entry) in tar.entries().unwrap().enumerate() {
            let entry = entry.unwrap();
            let expected_path = Path::new(match i {
                0 => "0000-00-00",
                1 => "0000-00-00/README.md",
                2 => "0000-00-00/data",
                3 => "0000-00-00/data/crates.csv",
                4 => "0000-00-00/data/users.csv", // alphabetically after crates.csv
                5 => "0000-00-00/data/crate_owners.csv", // depends on crates.csv and users.csv
                _ => panic!("unexpected extra tar entry: {:?}", entry.path()),
            });
            assert_eq!(entry.path().unwrap(), expected_path, "entry {}", i);
        }
    }
}
<commit_msg>worker::dump_db: Use `tracing` macros instead of `println!()`<commit_after>use std::{
    fs::{self, File},
    path::{Path, PathBuf},
};

use self::configuration::VisibilityConfig;
use crate::{
    background_jobs::Environment,
    uploaders::{UploadBucket, Uploader},
};
use reqwest::header;
use swirl::PerformError;

/// Create CSV dumps of the public information in the database, wrap them in a
/// tarball and upload to S3.
#[swirl::background_job]
pub fn dump_db(
    env: &Environment,
    database_url: String,
    target_name: String,
) -> Result<(), PerformError> {
    let directory = DumpDirectory::create()?;

    info!("Begin exporting database");
    directory.populate(&database_url)?;

    info!("Creating tarball");
    let tarball = DumpTarball::create(&directory.export_dir)?;

    info!("Uploading tarball");
    let size = tarball.upload(&target_name, &env.uploader)?;
    info!("Database dump uploaded {} bytes to {}.", size, &target_name);
    Ok(())
}

/// Manage the export directory.
///
/// Create the directory, populate it with the psql scripts and CSV dumps, and
/// make sure it gets deleted again even in the case of an error.
#[derive(Debug)]
pub struct DumpDirectory {
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub export_dir: PathBuf,
}

impl DumpDirectory {
    pub fn create() -> Result<Self, PerformError> {
        let timestamp = chrono::Utc::now();
        let timestamp_str = timestamp.format("%Y-%m-%d-%H%M%S").to_string();
        let export_dir = std::env::temp_dir().join("dump-db").join(timestamp_str);
        debug!(?export_dir, "Creating database dump folder…");
        std::fs::create_dir_all(&export_dir)?;
        Ok(Self {
            timestamp,
            export_dir,
        })
    }

    pub fn populate(&self, database_url: &str) -> Result<(), PerformError> {
        self.add_readme()?;
        self.add_metadata()?;
        self.dump_schema(database_url)?;
        self.dump_db(database_url)
    }

    fn add_readme(&self) -> Result<(), PerformError> {
        use std::io::Write;

        let path = self.export_dir.join("README.md");
        debug!(?path, "Writing README.md file…");
        let mut readme = File::create(path)?;
        readme.write_all(include_bytes!("dump_db/readme_for_tarball.md"))?;
        Ok(())
    }

    fn add_metadata(&self) -> Result<(), PerformError> {
        #[derive(Serialize)]
        struct Metadata<'a> {
            timestamp: &'a chrono::DateTime<chrono::Utc>,
            crates_io_commit: String,
        }
        let metadata = Metadata {
            timestamp: &self.timestamp,
            crates_io_commit: dotenv::var("HEROKU_SLUG_COMMIT")
                .unwrap_or_else(|_| "unknown".to_owned()),
        };
        let path = self.export_dir.join("metadata.json");
        debug!(?path, "Writing metadata.json file…");
        let file = File::create(path)?;
        serde_json::to_writer_pretty(file, &metadata)?;
        Ok(())
    }

    pub fn dump_schema(&self, database_url: &str) -> Result<(), PerformError> {
        let path = self.export_dir.join("schema.sql");
        debug!(?path, "Writing schema.sql file…");
        let schema_sql = File::create(path)?;

        let status = std::process::Command::new("pg_dump")
            .arg("--schema-only")
            .arg("--no-owner")
            .arg("--no-acl")
            .arg(database_url)
            .stdout(schema_sql)
            .spawn()?
            .wait()?;
        if !status.success() {
            return Err("pg_dump did not finish successfully.".into());
        }
        Ok(())
    }

    pub fn dump_db(&self, database_url: &str) -> Result<(), PerformError> {
        debug!("Generating export.sql and import.sql files…");
        let export_script = self.export_dir.join("export.sql");
        let import_script = self.export_dir.join("import.sql");
        gen_scripts::gen_scripts(&export_script, &import_script)?;

        debug!("Filling data folder…");
        std::fs::create_dir(self.export_dir.join("data"))?;
        run_psql(&export_script, database_url)
    }
}

impl Drop for DumpDirectory {
    fn drop(&mut self) {
        std::fs::remove_dir_all(&self.export_dir).unwrap();
    }
}

pub fn run_psql(script: &Path, database_url: &str) -> Result<(), PerformError> {
    debug!(?script, "Running psql script…");
    let psql_script = File::open(script)?;
    let psql = std::process::Command::new("psql")
        .arg(database_url)
        .current_dir(script.parent().unwrap())
        .stdin(psql_script)
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::piped())
        .spawn()?;
    let output = psql.wait_with_output()?;
    let stderr = String::from_utf8_lossy(&output.stderr);
    if stderr.contains("ERROR") {
        return Err(format!("Error while executing psql: {stderr}").into());
    }
    if !output.status.success() {
        return Err("psql did not finish successfully.".into());
    }
    Ok(())
}

/// Manage the tarball of the database dump.
///
/// Create the tarball, upload it to S3, and make sure it gets deleted.
struct DumpTarball {
    tarball_path: PathBuf,
}

impl DumpTarball {
    fn create(export_dir: &Path) -> Result<Self, PerformError> {
        let tarball_path = export_dir.with_extension("tar.gz");
        let tarfile = File::create(&tarball_path)?;
        let result = Self { tarball_path };
        let encoder = flate2::write::GzEncoder::new(tarfile, flate2::Compression::default());

        let mut archive = tar::Builder::new(encoder);
        let tar_top_dir = PathBuf::from(export_dir.file_name().unwrap());
        archive.append_dir(&tar_top_dir, export_dir)?;

        // Append readme, metadata, schemas.
        for entry in fs::read_dir(export_dir)? {
            let entry = entry?;
            let file_type = entry.file_type()?;
            if file_type.is_file() {
                let name_in_tar = tar_top_dir.join(entry.file_name());
                archive.append_path_with_name(entry.path(), name_in_tar)?;
            }
        }

        // Append topologically sorted tables to make it possible to pipeline
        // importing with gz extraction.
        archive.append_dir(tar_top_dir.join("data"), export_dir.join("data"))?;
        for table in VisibilityConfig::get().topological_sort() {
            let csv_path = export_dir.join("data").join(table).with_extension("csv");
            if csv_path.exists() {
                let name_in_tar = tar_top_dir.join("data").join(table).with_extension("csv");
                archive.append_path_with_name(csv_path, name_in_tar)?;
            }
        }

        Ok(result)
    }

    fn upload(&self, target_name: &str, uploader: &Uploader) -> Result<u64, PerformError> {
        let client = reqwest::blocking::Client::new();
        let tarfile = File::open(&self.tarball_path)?;
        let content_length = tarfile.metadata()?.len();
        // TODO Figure out the correct content type.
        uploader.upload(
            &client,
            target_name,
            tarfile,
            "application/gzip",
            header::HeaderMap::new(),
            UploadBucket::Default,
        )?;
        Ok(content_length)
    }
}

impl Drop for DumpTarball {
    fn drop(&mut self) {
        std::fs::remove_file(&self.tarball_path).unwrap();
    }
}

mod configuration;
mod gen_scripts;

#[cfg(test)]
mod tests {
    use super::*;
    use flate2::read::GzDecoder;
    use tar::Archive;

    #[test]
    fn test_dump_tarball() {
        let tempdir = tempfile::Builder::new()
            .prefix("DumpTarball")
            .tempdir()
            .unwrap();
        let p = tempdir.path().join("0000-00-00");

        fs::create_dir(&p).unwrap();
        fs::write(p.join("README.md"), "# crates.io Database Dump\n").unwrap();
        fs::create_dir(p.join("data")).unwrap();
        fs::write(p.join("data").join("crates.csv"), "").unwrap();
        fs::write(p.join("data").join("crate_owners.csv"), "").unwrap();
        fs::write(p.join("data").join("users.csv"), "").unwrap();

        let tarball = DumpTarball::create(&p).unwrap();
        let gz = GzDecoder::new(File::open(&*tarball.tarball_path).unwrap());
        let mut tar = Archive::new(gz);

        for (i, entry) in tar.entries().unwrap().enumerate() {
            let entry = entry.unwrap();
            let expected_path = Path::new(match i {
                0 => "0000-00-00",
                1 => "0000-00-00/README.md",
                2 => "0000-00-00/data",
                3 => "0000-00-00/data/crates.csv",
                4 => "0000-00-00/data/users.csv", // alphabetically after crates.csv
                5 => "0000-00-00/data/crate_owners.csv", // depends on crates.csv and users.csv
                _ => panic!("unexpected extra tar entry: {:?}", entry.path()),
            });
            assert_eq!(entry.path().unwrap(), expected_path, "entry {}", i);
        }
    }
}
<|endoftext|>


<commit_before>use rusqlite::types::ToSql;

use errors::wallet::WalletQueryError;
use services::wallet::language::{Operator,TagName,TargetValue};

// Translates Wallet Query Language to SQL
// WQL input is provided as a reference to a top level Operator
// Result is a tuple of query string and query arguments
pub fn wql_to_sql<'a>(class: &'a Vec<u8>, op: &'a Operator, options: Option<&str>) -> Result<(String, Vec<&'a ToSql>), WalletQueryError> {
    let mut arguments: Vec<&ToSql> = Vec::new();
    arguments.push(class);
    let clause_string = operator_to_sql(op, &mut arguments)?;
    let mut query_string = "SELECT i.id, i.name, i.value, i.key, i.type FROM items as i WHERE i.type = ?".to_string();
    if !clause_string.is_empty() {
        query_string.push_str(" AND ");
        query_string.push_str(&clause_string);
    }
    Ok((query_string, arguments))
}

pub fn wql_to_sql_count<'a>(class: &'a Vec<u8>, op: &'a Operator) -> Result<(String, Vec<&'a ToSql>), WalletQueryError> {
    let mut arguments: Vec<&ToSql> = Vec::new();
    arguments.push(class);
    let clause_string = operator_to_sql(op, &mut arguments)?;
    let mut query_string = "SELECT count(*) FROM items as i WHERE i.type = ?".to_string();
    if !clause_string.is_empty() {
        query_string.push_str(" AND ");
        query_string.push_str(&clause_string);
    }
    Ok((query_string, arguments))
}

fn operator_to_sql<'a>(op: &'a Operator, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match *op {
        Operator::Eq(ref tag_name, ref target_value) => eq_to_sql(tag_name, target_value, arguments),
        Operator::Neq(ref tag_name, ref target_value) => neq_to_sql(tag_name, target_value, arguments),
        Operator::Gt(ref tag_name, ref target_value) => gt_to_sql(tag_name, target_value, arguments),
        Operator::Gte(ref tag_name, ref target_value) => gte_to_sql(tag_name, target_value, arguments),
        Operator::Lt(ref tag_name, ref target_value) => lt_to_sql(tag_name, target_value, arguments),
        Operator::Lte(ref tag_name, ref target_value) => lte_to_sql(tag_name, target_value, arguments),
        Operator::Like(ref tag_name, ref target_value) => like_to_sql(tag_name, target_value, arguments),
        Operator::In(ref tag_name, ref target_values) => in_to_sql(tag_name, target_values, arguments),
        Operator::And(ref suboperators) => and_to_sql(suboperators, arguments),
        Operator::Or(ref suboperators) => or_to_sql(suboperators, arguments),
        Operator::Not(ref suboperator) => not_to_sql(suboperator, arguments),
    }
}

fn eq_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value = ?))".to_string())
        },
        (&TagName::EncryptedTagName(ref queried_name), &TargetValue::Encrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_encrypted WHERE name = ? AND value = ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for equality operator".to_string()))
    }
}

fn neq_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value != ?))".to_string())
        },
        (&TagName::EncryptedTagName(ref queried_name), &TargetValue::Encrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_encrypted WHERE name = ? AND value != ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for inequality operator".to_string()))
    }
}

fn gt_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value > ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $gt operator".to_string()))
    }
}

fn gte_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value >= ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $gte operator".to_string()))
    }
}

fn lt_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value < ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $lte operator".to_string()))
    }
}

fn lte_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value <= ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $lte operator".to_string()))
    }
}

fn like_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value LIKE ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $like operator".to_string()))
    }
}

fn regex_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value REGEXP ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $regex operator".to_string()))
    }
}

fn in_to_sql<'a>(name: &'a TagName, values: &'a Vec<TargetValue>, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    let mut in_string = String::new();
    match name {
        &TagName::PlainTagName(ref queried_name) => {
            in_string.push_str("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value IN (");
            arguments.push(queried_name);

            for (index, value) in values.iter().enumerate() {
                if let &TargetValue::Unencrypted(ref target) = value {
                    in_string.push_str("?");
                    arguments.push(target);
                    if index < values.len() - 1 {
                        in_string.push(',');
                    }
                } else {
                    return Err(WalletQueryError::StructureErr("Encrypted tag value in $in for nonencrypted tag name".to_string()))
                }
            }

            Ok(in_string + ")))")
        },
        &TagName::EncryptedTagName(ref queried_name) => {
            in_string.push_str("(i.id in (SELECT item_id FROM tags_encrypted WHERE name = ? AND value IN (");
            arguments.push(queried_name);
            let index_before_last = values.len() - 2;

            for (index, value) in values.iter().enumerate() {
                if let &TargetValue::Encrypted(ref target) = value {
                    in_string.push_str("?");
                    arguments.push(target);
                    if index <= index_before_last {
                        in_string.push(',');
                    }
                } else {
                    return Err(WalletQueryError::StructureErr("Unencrypted tag value in $in for encrypted tag name".to_string()))
                }
            }

            Ok(in_string + ")))")
        },
    }
}

fn and_to_sql<'a>(suboperators: &'a [Operator], arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    join_operators(suboperators, " AND ", arguments)
}

fn or_to_sql<'a>(suboperators: &'a [Operator], arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    join_operators(suboperators, " OR ", arguments)
}

fn not_to_sql<'a>(suboperator: &'a Operator, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    let suboperator_string = operator_to_sql(suboperator, arguments)?;
    Ok("NOT (".to_string() + &suboperator_string + ")")
}

fn join_operators<'a>(operators: &'a [Operator], join_str: &str, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    let mut s = String::new();
    if operators.len() > 0 {
        s.push('(');
        for (index, operator) in operators.iter().enumerate() {
            let operator_string = operator_to_sql(operator, arguments)?;
            s.push_str(&operator_string);
            if index < operators.len() - 1 {
                s.push_str(join_str);
            }
        }
        s.push(')');
    }
    Ok(s)
}

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

    #[test]
    fn simple_and() {
        let condition_1 = Operator::And(vec![
            Operator::Eq(TagName::EncryptedTagName(vec![1,2,3]), TargetValue::Encrypted(vec![4,5,6])),
            Operator::Eq(TagName::PlainTagName(vec![7,8,9]), TargetValue::Unencrypted("spam".to_string())),
        ]);
        let condition_2 = Operator::And(vec![
            Operator::Eq(TagName::EncryptedTagName(vec![10,11,12]), TargetValue::Encrypted(vec![13,14,15])),
            Operator::Not(Box::new(Operator::Eq(TagName::PlainTagName(vec![16,17,18]), TargetValue::Unencrypted("eggs".to_string()))))
        ]);
        let query = Operator::Or(vec![condition_1, condition_2]);
        let class = vec![100,100,100];
        let (query, arguments) = wql_to_sql(&class, &query, None).unwrap();
    }
}<commit_msg>Cleanup<commit_after>use rusqlite::types::ToSql;

use errors::wallet::WalletQueryError;
use services::wallet::language::{Operator,TagName,TargetValue};

// Translates Wallet Query Language to SQL
// WQL input is provided as a reference to a top level Operator
// Result is a tuple of query string and query arguments
pub fn wql_to_sql<'a>(class: &'a Vec<u8>, op: &'a Operator, options: Option<&str>) -> Result<(String, Vec<&'a ToSql>), WalletQueryError> {
    let mut arguments: Vec<&ToSql> = Vec::new();
    arguments.push(class);
    let clause_string = operator_to_sql(op, &mut arguments)?;
    let mut query_string = "SELECT i.id, i.name, i.value, i.key, i.type FROM items as i WHERE i.type = ?".to_string();
    if !clause_string.is_empty() {
        query_string.push_str(" AND ");
        query_string.push_str(&clause_string);
    }
    Ok((query_string, arguments))
}

pub fn wql_to_sql_count<'a>(class: &'a Vec<u8>, op: &'a Operator) -> Result<(String, Vec<&'a ToSql>), WalletQueryError> {
    let mut arguments: Vec<&ToSql> = Vec::new();
    arguments.push(class);
    let clause_string = operator_to_sql(op, &mut arguments)?;
    let mut query_string = "SELECT count(*) FROM items as i WHERE i.type = ?".to_string();
    if !clause_string.is_empty() {
        query_string.push_str(" AND ");
        query_string.push_str(&clause_string);
    }
    Ok((query_string, arguments))
}

fn operator_to_sql<'a>(op: &'a Operator, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match *op {
        Operator::Eq(ref tag_name, ref target_value) => eq_to_sql(tag_name, target_value, arguments),
        Operator::Neq(ref tag_name, ref target_value) => neq_to_sql(tag_name, target_value, arguments),
        Operator::Gt(ref tag_name, ref target_value) => gt_to_sql(tag_name, target_value, arguments),
        Operator::Gte(ref tag_name, ref target_value) => gte_to_sql(tag_name, target_value, arguments),
        Operator::Lt(ref tag_name, ref target_value) => lt_to_sql(tag_name, target_value, arguments),
        Operator::Lte(ref tag_name, ref target_value) => lte_to_sql(tag_name, target_value, arguments),
        Operator::Like(ref tag_name, ref target_value) => like_to_sql(tag_name, target_value, arguments),
        Operator::In(ref tag_name, ref target_values) => in_to_sql(tag_name, target_values, arguments),
        Operator::And(ref suboperators) => and_to_sql(suboperators, arguments),
        Operator::Or(ref suboperators) => or_to_sql(suboperators, arguments),
        Operator::Not(ref suboperator) => not_to_sql(suboperator, arguments),
    }
}

fn eq_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value = ?))".to_string())
        },
        (&TagName::EncryptedTagName(ref queried_name), &TargetValue::Encrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_encrypted WHERE name = ? AND value = ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for equality operator".to_string()))
    }
}

fn neq_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value != ?))".to_string())
        },
        (&TagName::EncryptedTagName(ref queried_name), &TargetValue::Encrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_encrypted WHERE name = ? AND value != ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for inequality operator".to_string()))
    }
}

fn gt_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value > ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $gt operator".to_string()))
    }
}

fn gte_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value >= ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $gte operator".to_string()))
    }
}

fn lt_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value < ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $lte operator".to_string()))
    }
}

fn lte_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value <= ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $lte operator".to_string()))
    }
}

fn like_to_sql<'a>(name: &'a TagName, value: &'a TargetValue, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    match (name, value) {
        (&TagName::PlainTagName(ref queried_name), &TargetValue::Unencrypted(ref queried_value)) => {
            arguments.push(queried_name);
            arguments.push(queried_value);
            Ok("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value LIKE ?))".to_string())
        },
        _ => Err(WalletQueryError::StructureErr("Invalid combination of tag name and value for $like operator".to_string()))
    }
}

fn in_to_sql<'a>(name: &'a TagName, values: &'a Vec<TargetValue>, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    let mut in_string = String::new();
    match name {
        &TagName::PlainTagName(ref queried_name) => {
            in_string.push_str("(i.id in (SELECT item_id FROM tags_plaintext WHERE name = ? AND value IN (");
            arguments.push(queried_name);

            for (index, value) in values.iter().enumerate() {
                if let &TargetValue::Unencrypted(ref target) = value {
                    in_string.push_str("?");
                    arguments.push(target);
                    if index < values.len() - 1 {
                        in_string.push(',');
                    }
                } else {
                    return Err(WalletQueryError::StructureErr("Encrypted tag value in $in for nonencrypted tag name".to_string()))
                }
            }

            Ok(in_string + ")))")
        },
        &TagName::EncryptedTagName(ref queried_name) => {
            in_string.push_str("(i.id in (SELECT item_id FROM tags_encrypted WHERE name = ? AND value IN (");
            arguments.push(queried_name);
            let index_before_last = values.len() - 2;

            for (index, value) in values.iter().enumerate() {
                if let &TargetValue::Encrypted(ref target) = value {
                    in_string.push_str("?");
                    arguments.push(target);
                    if index <= index_before_last {
                        in_string.push(',');
                    }
                } else {
                    return Err(WalletQueryError::StructureErr("Unencrypted tag value in $in for encrypted tag name".to_string()))
                }
            }

            Ok(in_string + ")))")
        },
    }
}

fn and_to_sql<'a>(suboperators: &'a [Operator], arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    join_operators(suboperators, " AND ", arguments)
}

fn or_to_sql<'a>(suboperators: &'a [Operator], arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    join_operators(suboperators, " OR ", arguments)
}

fn not_to_sql<'a>(suboperator: &'a Operator, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    let suboperator_string = operator_to_sql(suboperator, arguments)?;
    Ok("NOT (".to_string() + &suboperator_string + ")")
}

fn join_operators<'a>(operators: &'a [Operator], join_str: &str, arguments: &mut Vec<&'a ToSql>) -> Result<String, WalletQueryError> {
    let mut s = String::new();
    if operators.len() > 0 {
        s.push('(');
        for (index, operator) in operators.iter().enumerate() {
            let operator_string = operator_to_sql(operator, arguments)?;
            s.push_str(&operator_string);
            if index < operators.len() - 1 {
                s.push_str(join_str);
            }
        }
        s.push(')');
    }
    Ok(s)
}

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

    #[test]
    fn simple_and() {
        let condition_1 = Operator::And(vec![
            Operator::Eq(TagName::EncryptedTagName(vec![1,2,3]), TargetValue::Encrypted(vec![4,5,6])),
            Operator::Eq(TagName::PlainTagName(vec![7,8,9]), TargetValue::Unencrypted("spam".to_string())),
        ]);
        let condition_2 = Operator::And(vec![
            Operator::Eq(TagName::EncryptedTagName(vec![10,11,12]), TargetValue::Encrypted(vec![13,14,15])),
            Operator::Not(Box::new(Operator::Eq(TagName::PlainTagName(vec![16,17,18]), TargetValue::Unencrypted("eggs".to_string()))))
        ]);
        let query = Operator::Or(vec![condition_1, condition_2]);
        let class = vec![100,100,100];
        let (query, arguments) = wql_to_sql(&class, &query, None).unwrap();
    }
}<|endoftext|>