<commit_before>// Parts modified from https://github.com/elastic-rs/elastic
// Licensed under Apache 2.0: https://github.com/elastic-rs/elastic/blob/51298dd64278f34d2db911bd1a35eb757c336198/LICENSE-APACHE

use crate::api_generator::code_gen::url::url_builder::{IntoExpr, UrlBuilder};
use crate::api_generator::{code_gen::*, ApiEndpoint, Path};
use inflector::Inflector;
use syn;

/// Builder for request url parts enum
///
/// The output of this structure is an enum that only accepts valid parameter combinations,
/// based on what's given in the paths for an endpoint.
#[derive(Debug, Clone)]
pub struct EnumBuilder<'a> {
    ident: syn::Ident,
    api_name: String,
    variants: Vec<syn::Variant>,
    paths: Vec<&'a Path>,
    has_lifetime: bool,
}

impl<'a> EnumBuilder<'a> {
    pub fn new(prefix: &str) -> Self {
        let name = Self::name(prefix);
        let api_name = split_on_pascal_case(prefix);
        EnumBuilder {
            ident: ident(name),
            api_name,
            variants: vec![],
            paths: vec![],
            has_lifetime: false,
        }
    }

    fn name(prefix: &str) -> String {
        format!("{}Parts", prefix.to_pascal_case())
    }

    /// Whether this instance already contains a path with parts matching the given path
    fn contains_path_with_parts(&self, path: &'a Path) -> bool {
        let params = path.path.params();
        self.paths.iter().any(|&p| p.path.params() == params)
    }

    /// Whether this instance contains only a single path with no parts
    pub fn contains_single_parameterless_part(&self) -> bool {
        match self.paths.len() {
            1 => self.paths[0].parts.is_empty(),
            _ => false,
        }
    }

    pub fn with_path(mut self, path: &'a Path) -> Self {
        if !self.contains_path_with_parts(path) {
            let variant = match &path.parts.len() {
                0 => Self::parts_none(),
                _ => {
                    self.has_lifetime = true;
                    Self::parts(&path)
                }
            };

            self.variants.push(variant);
            self.paths.push(path);
        }

        self
    }

    /// AST for a parts variant.
    fn parts(path: &Path) -> syn::Variant {
        let params = &path.path.params();

        let name = params
            .iter()
            .map(|k| k.to_pascal_case())
            .collect::<Vec<_>>()
            .join("");

        let doc = match params.len() {
            1 => doc(params[0].replace("_", " ").to_pascal_case()),
            n => {
                let mut d: String = params
                    .iter()
                    .enumerate()
                    .filter(|&(i, _)| i != n - 1)
                    .map(|(_, e)| e.replace("_", " ").to_pascal_case())
                    .collect::<Vec<_>>()
                    .join(", ");

                d.push_str(
                    format!(" and {}", params[n - 1].replace("_", " ").to_pascal_case()).as_str(),
                );
                doc(d)
            }
        };

        syn::Variant {
            ident: ident(name),
            attrs: vec![doc],
            discriminant: None,
            data: syn::VariantData::Tuple(
                path.path
                    .params()
                    .iter()
                    .map(|&p| {
                        let ty = path.parts[p].ty;
                        syn::Field {
                            ident: None,
                            vis: syn::Visibility::Inherited,
                            attrs: vec![],
                            ty: typekind_to_ty(p, ty, true),
                        }
                    })
                    .collect(),
            ),
        }
    }

    /// AST for a `None` parts variant.
    fn parts_none() -> syn::Variant {
        syn::Variant {
            ident: ident("None"),
            attrs: vec![doc("No parts".into())],
            data: syn::VariantData::Unit,
            discriminant: None,
        }
    }

    fn match_path(ty: &syn::Ty, variant: &syn::Variant) -> syn::Path {
        let mut path = ty.get_path().to_owned();
        // Remove lifetimes from the enum type.
        for segment in &mut path.segments {
            segment.parameters = syn::PathParameters::none();
        }

        path.segments
            .push(syn::PathSegment::from(variant.ident.to_string()));
        path
    }

    /// Get the field names for the enum tuple variant to match.
    fn match_fields(path: &Path) -> Vec<syn::Pat> {
        path.path
            .params()
            .iter()
            .map(|&p| {
                syn::Pat::Ident(
                    syn::BindingMode::ByRef(syn::Mutability::Immutable),
                    ident(valid_name(p)),
                    None,
                )
            })
            .collect()
    }

    /// Build this enum and return ASTs for its type, struct declaration and impl
    pub fn build(self) -> (syn::Ty, syn::Item, syn::Item) {
        let variants = match self.variants.len() {
            0 => vec![Self::parts_none()],
            _ => self.variants,
        };

        let (enum_ty, generics) = {
            if self.has_lifetime {
                (ty_a(self.ident.as_ref()), generics_a())
            } else {
                (ty(self.ident.as_ref()), generics_none())
            }
        };

        let enum_impl = {
            let mut arms = Vec::new();
            for (variant, &path) in variants.iter().zip(self.paths.iter()) {
                let match_path = Self::match_path(&enum_ty, variant);
                let fields = Self::match_fields(path);

                let arm = match fields.len() {
                    0 => syn::Pat::Path(None, match_path),
                    _ => syn::Pat::TupleStruct(match_path, fields, None),
                };

                let body = UrlBuilder::new(path).build();
                arms.push(syn::Arm {
                    attrs: vec![],
                    pats: vec![arm],
                    guard: None,
                    body: Box::new(body),
                });
            }
            let match_expr: syn::Expr =
                syn::ExprKind::Match(Box::new(path_none("self").into_expr()), arms).into();

            let fn_decl = syn::FnDecl {
                inputs: vec![syn::FnArg::SelfValue(syn::Mutability::Immutable)],
                output: syn::FunctionRetTy::Ty(ty("Cow<'static, str>")),
                variadic: false,
            };

            let item = syn::ImplItem {
                ident: ident("url"),
                vis: syn::Visibility::Public,
                defaultness: syn::Defaultness::Final,
                attrs: vec![doc(format!(
                    "Builds a relative URL path to the {} API",
                    self.api_name
                ))],
                node: syn::ImplItemKind::Method(
                    syn::MethodSig {
                        unsafety: syn::Unsafety::Normal,
                        constness: syn::Constness::NotConst,
                        abi: None,
                        decl: fn_decl,
                        generics: generics_none(),
                    },
                    syn::Block {
                        stmts: vec![match_expr.into_stmt()],
                    },
                ),
            };

            syn::Item {
                ident: ident(""),
                vis: syn::Visibility::Public,
                attrs: vec![],
                node: syn::ItemKind::Impl(
                    syn::Unsafety::Normal,
                    syn::ImplPolarity::Positive,
                    generics.clone(),
                    None,
                    Box::new(enum_ty.clone()),
                    vec![item],
                ),
            }
        };

        let enum_decl = syn::Item {
            ident: self.ident,
            vis: syn::Visibility::Public,
            attrs: vec![
                syn::Attribute {
                    is_sugared_doc: false,
                    style: syn::AttrStyle::Outer,
                    value: syn::MetaItem::List(
                        ident("derive"),
                        vec![
                            syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ident("Debug"))),
                            syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ident("Clone"))),
                            syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ident("PartialEq"))),
                        ],
                    ),
                },
                doc(format!("API parts for the {} API", self.api_name)),
            ],
            node: syn::ItemKind::Enum(variants, generics),
        };

        (enum_ty, enum_decl, enum_impl)
    }
}

impl<'a> From<&'a (String, ApiEndpoint)> for EnumBuilder<'a> {
    fn from(value: &'a (String, ApiEndpoint)) -> Self {
        let endpoint = &value.1;
        let mut builder = EnumBuilder::new(value.0.to_pascal_case().as_ref());
        for path in &endpoint.url.paths {
            builder = builder.with_path(&path);
        }

        builder
    }
}

#[cfg(test)]
mod tests {
    #![cfg_attr(rustfmt, rustfmt_skip)]

    use super::*;
    use crate::api_generator::{Url, Path, HttpMethod, Body, Deprecated, Type, TypeKind, Documentation, ast_eq};
    use std::collections::BTreeMap;
    use crate::api_generator::code_gen::url::url_builder::PathString;

    #[test]
    fn generate_parts_enum_from_endpoint() {
        let endpoint = (
            "search".to_string(),
            ApiEndpoint {
                documentation: Documentation {
                    description: None,
                    url: None,
                },
                stability: "stable".to_string(),
                url: Url {
                    paths: vec![
                        Path {
                            path: PathString("/_search".to_string()),
                            methods: vec![HttpMethod::Get, HttpMethod::Post],
                            parts: BTreeMap::new(),
                            deprecated: None,
                        },
                        Path {
                            path: PathString("/{index}/_search".to_string()),
                            methods: vec![HttpMethod::Get, HttpMethod::Post],
                            parts: {
                                let mut map = BTreeMap::new();
                                map.insert("index".to_string(), Type {
                                    ty: TypeKind::List,
                                    description: Some("A comma-separated list of document types to search".to_string()),
                                    options: vec![],
                                    default: None,
                                });
                                map
                            },
                            deprecated: None,
                        },
                        Path {
                            path: PathString("/{index}/{type}/_search".to_string()),
                            methods: vec![HttpMethod::Get, HttpMethod::Post],
                            parts: {
                                let mut map = BTreeMap::new();
                                map.insert("index".to_string(), Type {
                                    ty: TypeKind::List,
                                    description: Some("A comma-separated list of index names to search".to_string()),
                                    options: vec![],
                                    default: None,
                                });
                                map.insert("type".to_string(), Type {
                                    ty: TypeKind::List,
                                    description: Some("A comma-separated list of document types to search".to_string()),
                                    options: vec![],
                                    default: None,
                                });
                                map
                            },
                            deprecated: Some(Deprecated {
                                version: "7.0.0".to_string(),
                                description: "types are going away".to_string()
                            }),
                        },
                    ],
                },
                params: BTreeMap::new(),
                body: Some(Body {
                    description: Some("The search request".to_string()),
                    required: Some(false),
                    serialize: None
                }),
            },
        );

        let (enum_ty, enum_decl, enum_impl) = EnumBuilder::from(&endpoint).build();

        assert_eq!(ty_a("SearchParts"), enum_ty);

        let expected_decl = quote!(
            #[derive(Debug, Clone, PartialEq)]
            #[doc = "API parts for the Search API"]
            pub enum SearchParts<'a> {
                None,
                Index(&'a [&'a str]),
                IndexType(&'a [&'a str], &'a [&'a str]),
            }
        );

        ast_eq(expected_decl, enum_decl);

        let expected_impl = quote!(
            impl<'a> SearchParts<'a> {
                #[doc = "Builds a relative URL path to the Search API"]
                pub fn url(self) -> Cow<'static, str> {
                    match self {
                        SearchParts::None => "/_search".into(),
                        SearchParts::Index(ref index) => {
                            let index_str = index.join(",");
                            let mut p = String::with_capacity(9usize + index_str.len());
                            p.push_str("/");
                            p.push_str(index_str.as_ref());
                            p.push_str("/_search");
                            p.into()
                        }
                        SearchParts::IndexType(ref index, ref ty) => {
                            let index_str = index.join(",");
                            let ty_str = ty.join(",");
                            let mut p = String::with_capacity(10usize + index_str.len() + ty_str.len());
                            p.push_str("/");
                            p.push_str(index_str.as_ref());
                            p.push_str("/");
                            p.push_str(ty_str.as_ref());
                            p.push_str("/_search");
                            p.into()
                        }
                    }
                }
            }
        );

        ast_eq(expected_impl, enum_impl);
    }
}
<commit_msg>Add doc attributes to expected test output<commit_after>// Parts modified from https://github.com/elastic-rs/elastic
// Licensed under Apache 2.0: https://github.com/elastic-rs/elastic/blob/51298dd64278f34d2db911bd1a35eb757c336198/LICENSE-APACHE

use crate::api_generator::code_gen::url::url_builder::{IntoExpr, UrlBuilder};
use crate::api_generator::{code_gen::*, ApiEndpoint, Path};
use inflector::Inflector;
use syn;

/// Builder for request url parts enum
///
/// The output of this structure is an enum that only accepts valid parameter combinations,
/// based on what's given in the paths for an endpoint.
#[derive(Debug, Clone)]
pub struct EnumBuilder<'a> {
    ident: syn::Ident,
    api_name: String,
    variants: Vec<syn::Variant>,
    paths: Vec<&'a Path>,
    has_lifetime: bool,
}

impl<'a> EnumBuilder<'a> {
    pub fn new(prefix: &str) -> Self {
        let name = Self::name(prefix);
        let api_name = split_on_pascal_case(prefix);
        EnumBuilder {
            ident: ident(name),
            api_name,
            variants: vec![],
            paths: vec![],
            has_lifetime: false,
        }
    }

    fn name(prefix: &str) -> String {
        format!("{}Parts", prefix.to_pascal_case())
    }

    /// Whether this instance already contains a path with parts matching the given path
    fn contains_path_with_parts(&self, path: &'a Path) -> bool {
        let params = path.path.params();
        self.paths.iter().any(|&p| p.path.params() == params)
    }

    /// Whether this instance contains only a single path with no parts
    pub fn contains_single_parameterless_part(&self) -> bool {
        match self.paths.len() {
            1 => self.paths[0].parts.is_empty(),
            _ => false,
        }
    }

    pub fn with_path(mut self, path: &'a Path) -> Self {
        if !self.contains_path_with_parts(path) {
            let variant = match &path.parts.len() {
                0 => Self::parts_none(),
                _ => {
                    self.has_lifetime = true;
                    Self::parts(&path)
                }
            };

            self.variants.push(variant);
            self.paths.push(path);
        }

        self
    }

    /// AST for a parts variant.
    fn parts(path: &Path) -> syn::Variant {
        let params = &path.path.params();

        let name = params
            .iter()
            .map(|k| k.to_pascal_case())
            .collect::<Vec<_>>()
            .join("");

        let doc = match params.len() {
            1 => doc(params[0].replace("_", " ").to_pascal_case()),
            n => {
                let mut d: String = params
                    .iter()
                    .enumerate()
                    .filter(|&(i, _)| i != n - 1)
                    .map(|(_, e)| e.replace("_", " ").to_pascal_case())
                    .collect::<Vec<_>>()
                    .join(", ");

                d.push_str(
                    format!(" and {}", params[n - 1].replace("_", " ").to_pascal_case()).as_str(),
                );
                doc(d)
            }
        };

        syn::Variant {
            ident: ident(name),
            attrs: vec![doc],
            discriminant: None,
            data: syn::VariantData::Tuple(
                path.path
                    .params()
                    .iter()
                    .map(|&p| {
                        let ty = path.parts[p].ty;
                        syn::Field {
                            ident: None,
                            vis: syn::Visibility::Inherited,
                            attrs: vec![],
                            ty: typekind_to_ty(p, ty, true),
                        }
                    })
                    .collect(),
            ),
        }
    }

    /// AST for a `None` parts variant.
    fn parts_none() -> syn::Variant {
        syn::Variant {
            ident: ident("None"),
            attrs: vec![doc("No parts".into())],
            data: syn::VariantData::Unit,
            discriminant: None,
        }
    }

    fn match_path(ty: &syn::Ty, variant: &syn::Variant) -> syn::Path {
        let mut path = ty.get_path().to_owned();
        // Remove lifetimes from the enum type.
        for segment in &mut path.segments {
            segment.parameters = syn::PathParameters::none();
        }

        path.segments
            .push(syn::PathSegment::from(variant.ident.to_string()));
        path
    }

    /// Get the field names for the enum tuple variant to match.
    fn match_fields(path: &Path) -> Vec<syn::Pat> {
        path.path
            .params()
            .iter()
            .map(|&p| {
                syn::Pat::Ident(
                    syn::BindingMode::ByRef(syn::Mutability::Immutable),
                    ident(valid_name(p)),
                    None,
                )
            })
            .collect()
    }

    /// Build this enum and return ASTs for its type, struct declaration and impl
    pub fn build(self) -> (syn::Ty, syn::Item, syn::Item) {
        let variants = match self.variants.len() {
            0 => vec![Self::parts_none()],
            _ => self.variants,
        };

        let (enum_ty, generics) = {
            if self.has_lifetime {
                (ty_a(self.ident.as_ref()), generics_a())
            } else {
                (ty(self.ident.as_ref()), generics_none())
            }
        };

        let enum_impl = {
            let mut arms = Vec::new();
            for (variant, &path) in variants.iter().zip(self.paths.iter()) {
                let match_path = Self::match_path(&enum_ty, variant);
                let fields = Self::match_fields(path);

                let arm = match fields.len() {
                    0 => syn::Pat::Path(None, match_path),
                    _ => syn::Pat::TupleStruct(match_path, fields, None),
                };

                let body = UrlBuilder::new(path).build();
                arms.push(syn::Arm {
                    attrs: vec![],
                    pats: vec![arm],
                    guard: None,
                    body: Box::new(body),
                });
            }
            let match_expr: syn::Expr =
                syn::ExprKind::Match(Box::new(path_none("self").into_expr()), arms).into();

            let fn_decl = syn::FnDecl {
                inputs: vec![syn::FnArg::SelfValue(syn::Mutability::Immutable)],
                output: syn::FunctionRetTy::Ty(ty("Cow<'static, str>")),
                variadic: false,
            };

            let item = syn::ImplItem {
                ident: ident("url"),
                vis: syn::Visibility::Public,
                defaultness: syn::Defaultness::Final,
                attrs: vec![doc(format!(
                    "Builds a relative URL path to the {} API",
                    self.api_name
                ))],
                node: syn::ImplItemKind::Method(
                    syn::MethodSig {
                        unsafety: syn::Unsafety::Normal,
                        constness: syn::Constness::NotConst,
                        abi: None,
                        decl: fn_decl,
                        generics: generics_none(),
                    },
                    syn::Block {
                        stmts: vec![match_expr.into_stmt()],
                    },
                ),
            };

            syn::Item {
                ident: ident(""),
                vis: syn::Visibility::Public,
                attrs: vec![],
                node: syn::ItemKind::Impl(
                    syn::Unsafety::Normal,
                    syn::ImplPolarity::Positive,
                    generics.clone(),
                    None,
                    Box::new(enum_ty.clone()),
                    vec![item],
                ),
            }
        };

        let enum_decl = syn::Item {
            ident: self.ident,
            vis: syn::Visibility::Public,
            attrs: vec![
                syn::Attribute {
                    is_sugared_doc: false,
                    style: syn::AttrStyle::Outer,
                    value: syn::MetaItem::List(
                        ident("derive"),
                        vec![
                            syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ident("Debug"))),
                            syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ident("Clone"))),
                            syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ident("PartialEq"))),
                        ],
                    ),
                },
                doc(format!("API parts for the {} API", self.api_name)),
            ],
            node: syn::ItemKind::Enum(variants, generics),
        };

        (enum_ty, enum_decl, enum_impl)
    }
}

impl<'a> From<&'a (String, ApiEndpoint)> for EnumBuilder<'a> {
    fn from(value: &'a (String, ApiEndpoint)) -> Self {
        let endpoint = &value.1;
        let mut builder = EnumBuilder::new(value.0.to_pascal_case().as_ref());
        for path in &endpoint.url.paths {
            builder = builder.with_path(&path);
        }

        builder
    }
}

#[cfg(test)]
mod tests {
    #![cfg_attr(rustfmt, rustfmt_skip)]

    use super::*;
    use crate::api_generator::{Url, Path, HttpMethod, Body, Deprecated, Type, TypeKind, Documentation, ast_eq};
    use std::collections::BTreeMap;
    use crate::api_generator::code_gen::url::url_builder::PathString;

    #[test]
    fn generate_parts_enum_from_endpoint() {
        let endpoint = (
            "search".to_string(),
            ApiEndpoint {
                documentation: Documentation {
                    description: None,
                    url: None,
                },
                stability: "stable".to_string(),
                url: Url {
                    paths: vec![
                        Path {
                            path: PathString("/_search".to_string()),
                            methods: vec![HttpMethod::Get, HttpMethod::Post],
                            parts: BTreeMap::new(),
                            deprecated: None,
                        },
                        Path {
                            path: PathString("/{index}/_search".to_string()),
                            methods: vec![HttpMethod::Get, HttpMethod::Post],
                            parts: {
                                let mut map = BTreeMap::new();
                                map.insert("index".to_string(), Type {
                                    ty: TypeKind::List,
                                    description: Some("A comma-separated list of document types to search".to_string()),
                                    options: vec![],
                                    default: None,
                                });
                                map
                            },
                            deprecated: None,
                        },
                        Path {
                            path: PathString("/{index}/{type}/_search".to_string()),
                            methods: vec![HttpMethod::Get, HttpMethod::Post],
                            parts: {
                                let mut map = BTreeMap::new();
                                map.insert("index".to_string(), Type {
                                    ty: TypeKind::List,
                                    description: Some("A comma-separated list of index names to search".to_string()),
                                    options: vec![],
                                    default: None,
                                });
                                map.insert("type".to_string(), Type {
                                    ty: TypeKind::List,
                                    description: Some("A comma-separated list of document types to search".to_string()),
                                    options: vec![],
                                    default: None,
                                });
                                map
                            },
                            deprecated: Some(Deprecated {
                                version: "7.0.0".to_string(),
                                description: "types are going away".to_string()
                            }),
                        },
                    ],
                },
                params: BTreeMap::new(),
                body: Some(Body {
                    description: Some("The search request".to_string()),
                    required: Some(false),
                    serialize: None
                }),
            },
        );

        let (enum_ty, enum_decl, enum_impl) = EnumBuilder::from(&endpoint).build();

        assert_eq!(ty_a("SearchParts"), enum_ty);

        let expected_decl = quote!(
            #[derive(Debug, Clone, PartialEq)]
            #[doc = "API parts for the Search API"]
            pub enum SearchParts<'a> {
                #[doc = "No parts"]
                None,
                #[doc = "Index"]
                Index(&'a [&'a str]),
                #[doc = "Index and Type"]
                IndexType(&'a [&'a str], &'a [&'a str]),
            }
        );

        ast_eq(expected_decl, enum_decl);

        let expected_impl = quote!(
            impl<'a> SearchParts<'a> {
                #[doc = "Builds a relative URL path to the Search API"]
                pub fn url(self) -> Cow<'static, str> {
                    match self {
                        SearchParts::None => "/_search".into(),
                        SearchParts::Index(ref index) => {
                            let index_str = index.join(",");
                            let mut p = String::with_capacity(9usize + index_str.len());
                            p.push_str("/");
                            p.push_str(index_str.as_ref());
                            p.push_str("/_search");
                            p.into()
                        }
                        SearchParts::IndexType(ref index, ref ty) => {
                            let index_str = index.join(",");
                            let ty_str = ty.join(",");
                            let mut p = String::with_capacity(10usize + index_str.len() + ty_str.len());
                            p.push_str("/");
                            p.push_str(index_str.as_ref());
                            p.push_str("/");
                            p.push_str(ty_str.as_ref());
                            p.push_str("/_search");
                            p.into()
                        }
                    }
                }
            }
        );

        ast_eq(expected_impl, enum_impl);
    }
}
<|endoftext|>


<commit_before>use std::ascii::AsciiExt;
use std::cmp::min;

pub fn compute_matches<'a>(choices: &'a Vec<String>, query: &str) -> Vec<&'a String> {
  struct ScoredChoice<'a> {
    score: f64,
    choice: &'a String,
  };
  let mut ret = Vec::new();
  for choice in choices.iter() {
    let score = score(choice.as_slice(), query);
    if score > 0_f64 {
      ret.push(ScoredChoice{ score: score, choice: choice });
    }
  }
  ret.sort_by(|x, y| y.score.partial_cmp(&x.score).unwrap());
  ret.iter().map(|s| s.choice).collect()
}

fn score(choice: &str, query: &str) -> f64 {
  if query.len() == 0 {
    return 1.0;
  }
  if choice.len() == 0 {
    return 0.0;
  }

  let query = query.to_ascii_lowercase();
  let choice = choice.to_ascii_lowercase();

  match compute_match_length(choice.as_slice(), query.as_slice()) {
    None => return 0.0,
    Some(match_length) => {
      let score = query.len() as f64 / match_length as f64;
      return score as f64 / choice.len() as f64;
    }
  }
}

fn compute_match_length(string: &str, chars: &str) -> Option<usize> {
  let first_char = chars.char_at(0);
  let rest = chars.slice(1, chars.len());
  let indices = find_char_in_string(string, first_char);

  let mut current_min = None;
  for i in indices.iter() {
    let last_index = find_end_of_match(string, rest, *i);
    if last_index.is_some() {
      let idx = last_index.unwrap() - *i + 1;
      if current_min.is_some() {
        let cm = min(current_min.unwrap(), idx);
        current_min = Some(cm);
      } else {
        current_min = Some(idx)
      }
    }
  }
  return current_min;
}

fn find_char_in_string(string: &str, char: char) -> Vec<usize> {
  let mut indices = Vec::new();
  let mut i = 0;
  for c in string.chars() {
    if c == char {
      indices.push(i);
    }
    i = i + 1;
  }
  return indices;
}

fn find_end_of_match(string: &str, rest_of_query: &str, first_index: usize) -> Option<usize> {
  let mut last_index = first_index + 1;
  for c in rest_of_query.chars() {
    let current_substring = string.slice_chars(last_index, string.len());
    match current_substring.find(c) {
      None => return None,
      Some(ref i) => {
        last_index += *i + 1;
      }
    }
  }
  return Some(last_index - 1);
}

#[test]
fn find_end_of_match_test() {
  assert_eq!(find_end_of_match("a", "a", 0), None);
  assert_eq!(find_end_of_match("ba", "a", 1), None);
  assert_eq!(find_end_of_match("aaa", "aa", 0), Some(2));
  assert_eq!(find_end_of_match("aaa", "b", 0), None);
  assert_eq!(find_end_of_match("this is a long match", "this is a match", 0), None);
  assert_eq!(find_end_of_match("this is a long match", "his is a match", 0), Some(19));
}

#[test]
fn basic_scoring() {
  assert_eq!(score("", "a"), 0.0);
  assert_eq!(score("a", ""), 1.0);
  assert_eq!(score("short", "longer"), 0.0);
  assert_eq!(score("a", "b"), 0.0);
  assert_eq!(score("ab", "ac"), 0.0);

  assert!(score("a", "a") > 0.0);
  assert!(score("ab", "a") > 0.0);
  assert!(score("ba", "a") > 0.0);
  assert!(score("bab", "a") > 0.0);
  assert!(score("babababab", "aaaa") > 0.0);

  assert_eq!(score("a", "a"), 1_f64 / "a".len() as f64);
  assert_eq!(score("ab", "ab"), 0.5);
  assert_eq!(score("a long string", "a long string"), 1_f64 / "a long string".len() as f64);
  assert_eq!(score("spec/search_spec.rb", "sear"), 1_f64 / "spec/search_spec.rb".len() as f64);
}

#[test]
fn character_matching() {
  assert!(score("/! symbols $^", "/!$^") > 0.0);

  assert_eq!(score("a", "A"), 1.0);
  assert_eq!(score("A", "a"), 1.0);

  assert_eq!(score("a", "aa"), 0.0);
}

#[test]
fn match_equality() {
  assert!(score("selecta.gemspec", "asp") > score("algorithm4_spec.rb", "asp"));
  assert!(score("README.md", "em") > score("benchmark.rb", "em"));
  assert!(score("search.rb", "sear") > score("spec/search_spec.rb", "sear"));

  assert!(score("fbb", "fbb") > score("foo bar baz", "fbb"));
  assert!(score("foo", "foo") > score("longer foo", "foo"));
  assert!(score("foo", "foo") > score("foo longer", "foo"));
  assert!(score("1/2/3/4", "1/2/3") > score("1/9/2/3/4", "1/2/3"));

  assert!(score("long 12 long", "12") > score("1 long 2", "12"));

  assert_eq!(score("121padding2", "12"), 1.0 / "121padding2".len() as f64);
  assert_eq!(score("1padding212", "12"), 1.0 / "1padding212".len() as f64);
}
<commit_msg>Use slice syntax<commit_after>use std::ascii::AsciiExt;
use std::cmp::min;

pub fn compute_matches<'a>(choices: &'a Vec<String>, query: &str) -> Vec<&'a String> {
  struct ScoredChoice<'a> {
    score: f64,
    choice: &'a String,
  };
  let mut ret = Vec::new();
  for choice in choices.iter() {
    let score = score(choice.as_slice(), query);
    if score > 0_f64 {
      ret.push(ScoredChoice{ score: score, choice: choice });
    }
  }
  ret.sort_by(|x, y| y.score.partial_cmp(&x.score).unwrap());
  ret.iter().map(|s| s.choice).collect()
}

fn score(choice: &str, query: &str) -> f64 {
  if query.len() == 0 {
    return 1.0;
  }
  if choice.len() == 0 {
    return 0.0;
  }

  let query = query.to_ascii_lowercase();
  let choice = choice.to_ascii_lowercase();

  match compute_match_length(choice.as_slice(), query.as_slice()) {
    None => return 0.0,
    Some(match_length) => {
      let score = query.len() as f64 / match_length as f64;
      return score as f64 / choice.len() as f64;
    }
  }
}

fn compute_match_length(string: &str, chars: &str) -> Option<usize> {
  let first_char = chars.char_at(0);
  let rest = &chars[1..chars.len()];
  let indices = find_char_in_string(string, first_char);

  let mut current_min = None;
  for i in indices.iter() {
    let last_index = find_end_of_match(string, rest, *i);
    if last_index.is_some() {
      let idx = last_index.unwrap() - *i + 1;
      if current_min.is_some() {
        let cm = min(current_min.unwrap(), idx);
        current_min = Some(cm);
      } else {
        current_min = Some(idx)
      }
    }
  }
  return current_min;
}

fn find_char_in_string(string: &str, char: char) -> Vec<usize> {
  let mut indices = Vec::new();
  let mut i = 0;
  for c in string.chars() {
    if c == char {
      indices.push(i);
    }
    i = i + 1;
  }
  return indices;
}

fn find_end_of_match(string: &str, rest_of_query: &str, first_index: usize) -> Option<usize> {
  let mut last_index = first_index + 1;
  for c in rest_of_query.chars() {
    let current_substring = string.slice_chars(last_index, string.len());
    match current_substring.find(c) {
      None => return None,
      Some(ref i) => {
        last_index += *i + 1;
      }
    }
  }
  return Some(last_index - 1);
}

#[test]
fn find_end_of_match_test() {
  assert_eq!(find_end_of_match("a", "a", 0), None);
  assert_eq!(find_end_of_match("ba", "a", 1), None);
  assert_eq!(find_end_of_match("aaa", "aa", 0), Some(2));
  assert_eq!(find_end_of_match("aaa", "b", 0), None);
  assert_eq!(find_end_of_match("this is a long match", "this is a match", 0), None);
  assert_eq!(find_end_of_match("this is a long match", "his is a match", 0), Some(19));
}

#[test]
fn basic_scoring() {
  assert_eq!(score("", "a"), 0.0);
  assert_eq!(score("a", ""), 1.0);
  assert_eq!(score("short", "longer"), 0.0);
  assert_eq!(score("a", "b"), 0.0);
  assert_eq!(score("ab", "ac"), 0.0);

  assert!(score("a", "a") > 0.0);
  assert!(score("ab", "a") > 0.0);
  assert!(score("ba", "a") > 0.0);
  assert!(score("bab", "a") > 0.0);
  assert!(score("babababab", "aaaa") > 0.0);

  assert_eq!(score("a", "a"), 1_f64 / "a".len() as f64);
  assert_eq!(score("ab", "ab"), 0.5);
  assert_eq!(score("a long string", "a long string"), 1_f64 / "a long string".len() as f64);
  assert_eq!(score("spec/search_spec.rb", "sear"), 1_f64 / "spec/search_spec.rb".len() as f64);
}

#[test]
fn character_matching() {
  assert!(score("/! symbols $^", "/!$^") > 0.0);

  assert_eq!(score("a", "A"), 1.0);
  assert_eq!(score("A", "a"), 1.0);

  assert_eq!(score("a", "aa"), 0.0);
}

#[test]
fn match_equality() {
  assert!(score("selecta.gemspec", "asp") > score("algorithm4_spec.rb", "asp"));
  assert!(score("README.md", "em") > score("benchmark.rb", "em"));
  assert!(score("search.rb", "sear") > score("spec/search_spec.rb", "sear"));

  assert!(score("fbb", "fbb") > score("foo bar baz", "fbb"));
  assert!(score("foo", "foo") > score("longer foo", "foo"));
  assert!(score("foo", "foo") > score("foo longer", "foo"));
  assert!(score("1/2/3/4", "1/2/3") > score("1/9/2/3/4", "1/2/3"));

  assert!(score("long 12 long", "12") > score("1 long 2", "12"));

  assert_eq!(score("121padding2", "12"), 1.0 / "121padding2".len() as f64);
  assert_eq!(score("1padding212", "12"), 1.0 / "1padding212".len() as f64);
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use errors;
use providers;
use providers::aws::AwsProvider;
use providers::azure::Azure;
use providers::cloudstack::configdrive::ConfigDrive;
use providers::cloudstack::network::CloudstackNetwork;
use providers::digitalocean::DigitalOceanProvider;
use providers::gcp::GcpProvider;
use providers::openstack::network::OpenstackProvider;
use providers::packet::PacketProvider;
use providers::vagrant_virtualbox::VagrantVirtualboxProvider;

macro_rules! box_result {
    ($exp:expr) => {
        Ok(Box::new($exp))
    };
}

/// `fetch_metadata` is the generic, top-level function that is used by the main
/// function to fetch metadata. The configured provider is passed in and this
/// function dispatches the call to the correct provider-specific fetch function
pub fn fetch_metadata(provider: &str) -> errors::Result<Box<providers::MetadataProvider>> {
    match provider {
        "azure" => box_result!(Azure::try_new()?),
        "cloudstack-metadata" => box_result!(CloudstackNetwork::try_new()?),
        "cloudstack-configdrive" => box_result!(ConfigDrive::try_new()?),
        "digitalocean" => box_result!(DigitalOceanProvider::try_new()?),
        "ec2" => box_result!(AwsProvider::try_new()?),
        "gce" => box_result!(GcpProvider::try_new()?),
        "openstack-metadata" => box_result!(OpenstackProvider::try_new()?),
        "packet" => box_result!(PacketProvider::try_new()?),
        "vagrant-virtualbox" => box_result!(VagrantVirtualboxProvider::new()),
        _ => Err(errors::ErrorKind::UnknownProvider(provider.to_owned()).into()),
    }
}
<commit_msg>src: accept aws/ec2/gce/gcp provider names depending on cl-legacy<commit_after>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use errors;
use providers;
use providers::aws::AwsProvider;
use providers::azure::Azure;
use providers::cloudstack::configdrive::ConfigDrive;
use providers::cloudstack::network::CloudstackNetwork;
use providers::digitalocean::DigitalOceanProvider;
use providers::gcp::GcpProvider;
use providers::openstack::network::OpenstackProvider;
use providers::packet::PacketProvider;
use providers::vagrant_virtualbox::VagrantVirtualboxProvider;

macro_rules! box_result {
    ($exp:expr) => {
        Ok(Box::new($exp))
    };
}

/// `fetch_metadata` is the generic, top-level function that is used by the main
/// function to fetch metadata. The configured provider is passed in and this
/// function dispatches the call to the correct provider-specific fetch function
pub fn fetch_metadata(provider: &str) -> errors::Result<Box<providers::MetadataProvider>> {
    match provider {
        #[cfg(not(feature = "cl-legacy"))]
        "aws" => box_result!(AwsProvider::try_new()?),
        "azure" => box_result!(Azure::try_new()?),
        "cloudstack-metadata" => box_result!(CloudstackNetwork::try_new()?),
        "cloudstack-configdrive" => box_result!(ConfigDrive::try_new()?),
        "digitalocean" => box_result!(DigitalOceanProvider::try_new()?),
        // FIXME: "ec2" is accepted in non-legacy mode temporarily for transition
        "ec2" => box_result!(AwsProvider::try_new()?),
        #[cfg(feature = "cl-legacy")]
        "gce" => box_result!(GcpProvider::try_new()?),
        #[cfg(not(feature = "cl-legacy"))]
        "gcp" => box_result!(GcpProvider::try_new()?),
        "openstack-metadata" => box_result!(OpenstackProvider::try_new()?),
        "packet" => box_result!(PacketProvider::try_new()?),
        "vagrant-virtualbox" => box_result!(VagrantVirtualboxProvider::new()),
        _ => Err(errors::ErrorKind::UnknownProvider(provider.to_owned()).into()),
    }
}
<|endoftext|>


<commit_before>use rustc_serialize::json;
use std::fs::File;
use std::path::PathBuf;
use std::sync::mpsc::Sender;

use datatype::{AccessToken, Config, Event, Error, Url, UpdateRequestId,
               UpdateReport, UpdateReportWithVin, Package,
               UpdateResultCode, UpdateState, PendingUpdateRequest};

use http_client::{Auth, HttpClient, HttpRequest};

fn vehicle_updates_endpoint(config: &Config, path: &str) -> Url {
    config.ota.server.join(&format!("/api/v1/vehicle_updates/{}/{}", config.auth.vin, path)).unwrap()
}

pub fn download_package_update(config: &Config,
                               client: &mut HttpClient,
                               token:  &AccessToken,
                               id:     &UpdateRequestId) -> Result<PathBuf, Error> {

    let req = HttpRequest::get(
        vehicle_updates_endpoint(config, &format!("{}/download", id)),
        Some(Auth::Token(token)),
    );

    let mut path = PathBuf::new();
    path.push(&config.ota.packages_dir);
    path.push(id);
    path.set_extension(config.ota.package_manager.extension());

    let mut file = try!(File::create(path.as_path()));

    try!(client.send_request_to(&req, &mut file));

    return Ok(path)

}

pub fn send_install_report(config: &Config,
                           client: &mut HttpClient,
                           token:  &AccessToken,
                           report: &UpdateReport) -> Result<(), Error> {

    let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report);
    let json            = try!(json::encode(&report_with_vin));

    let req = HttpRequest::post(
        vehicle_updates_endpoint(config, &format!("{}", report.update_id)),
        Some(Auth::Token(token)),
        Some(json)
    );

    let _: String = try!(client.send_request(&req));

    return Ok(())

}

pub fn get_package_updates(config: &Config,
                           client: &mut HttpClient,
                           token:  &AccessToken) -> Result<Vec<PendingUpdateRequest>, Error> {

    let req = HttpRequest::get(
        vehicle_updates_endpoint(&config, ""),
        Some(Auth::Token(token)),
    );

    let resp = try!(client.send_request(&req));

    return Ok(try!(json::decode::<Vec<PendingUpdateRequest>>(&resp)));
}

// XXX: Remove in favour of update_installed_packages()?
pub fn update_packages(config: &Config,
                     client: &mut HttpClient,
                     token:  &AccessToken,
                     pkgs:   &Vec<Package>) -> Result<(), Error> {
    let json = try!(json::encode(&pkgs));

    let req = HttpRequest::put(
        vehicle_updates_endpoint(config, "installed"),
        Some(Auth::Token(token)),
        Some(json),
    );

    let _: String = try!(client.send_request(&req));

    return Ok(())
}

pub fn update_installed_packages(config: &Config,
                               client: &mut HttpClient,
                               token:  &AccessToken) -> Result<(), Error> {

    let pkgs = try!(config.ota.package_manager.installed_packages());
    update_packages(config, client, token, &pkgs)

}

pub fn install_package_update(config:      &Config,
                              http_client: &mut HttpClient,
                              token:       &AccessToken,
                              id:          &UpdateRequestId,
                              tx:          &Sender<Event>) -> Result<UpdateReport, Error> {

    match download_package_update(config, http_client, token, id) {

        Ok(path) => {
            info!("Downloaded at {:?}. Installing...", path);
            try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)));

            let p = try!(path.to_str()
                         .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path))));

            match config.ota.package_manager.install_package(p) {

                Ok((code, output)) => {
                    try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)));
                    try!(update_installed_packages(config, http_client, token));
                    Ok(UpdateReport::new(id.clone(), code, output))
                }

                Err((code, output)) => {
                    try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output))));
                    Ok(UpdateReport::new(id.clone(), code, output))
                }

            }

        }

        Err(err) => {
            try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err))));
            Ok(UpdateReport::new(id.clone(),
                              UpdateResultCode::GENERAL_ERROR,
                              format!("Download failed: {:?}", err)))
        }
    }

}

#[cfg(test)]
mod tests {

    use std::fmt::Debug;
    use std::sync::mpsc::{channel, Receiver};
    use rustc_serialize::json;

    use super::*;
    use datatype::{AccessToken, Config, Event, OtaConfig, Package,
                   UpdateResultCode, UpdateState, PendingUpdateRequest};
    use http_client::TestHttpClient;
    use package_manager::PackageManager;

    fn test_token() -> AccessToken {
        AccessToken {
            access_token: "token".to_string(),
            token_type: "bar".to_string(),
            expires_in: 20,
            scope: vec![]
        }
    }

    fn test_package() -> Package {
        Package {
            name: "hey".to_string(),
            version: "1.2.3".to_string()
        }
    }

    #[test]
    fn test_update_packages_sends_authentication() {
        assert_eq!(update_packages(&Config::default(),
                                 &mut TestHttpClient::from(vec![""]),
                                 &test_token(),
                                 &vec![test_package()])
                   .unwrap(), ())
    }

    #[test]
    fn test_get_package_updates() {
        let pending_update = PendingUpdateRequest {
            id: "someid".to_string(),
            installPos: 0,
            packageId: Package {
                name: "fake-pkg".to_string(),
                version: "0.1.1".to_string()
            },
            createdAt: "2010-01-01".to_string()
        };

        let json_response = format!("[{}]",json::encode(&pending_update).unwrap());

        let updates: Vec<PendingUpdateRequest> = get_package_updates(&Config::default(),
                                       &mut TestHttpClient::from(vec![json_response.as_str()]),
                                       &test_token()).unwrap();

        let update_ids: Vec<String> = updates.iter().map(|p| p.id.clone()).collect();

        assert_eq!(update_ids, vec!["someid".to_string()])
    }

    #[test]
    #[ignore] // TODO: docker daemon requires user namespaces for this to work
    fn bad_packages_dir_download_package_update() {
        let mut config = Config::default();
        config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota };

        assert_eq!(format!("{}", download_package_update(&config,
                                                         &mut TestHttpClient::from(vec![""]),
                                                         &test_token(),
                                                         &"0".to_string())
                           .unwrap_err()),
                   "IO error: Permission denied (os error 13)")
    }

    #[test]
    fn bad_client_download_package_update() {
        assert_eq!(format!("{}",
                           download_package_update(&Config::default(),
                                                   &mut TestHttpClient::new(),
                                                   &test_token(),
                                                   &"0".to_string())
                           .unwrap_err()),
                   "Http client error: GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download")
    }

    fn assert_receiver_eq<X: PartialEq + Debug>(rx: Receiver<X>, xs: &[X]) {

        let mut xs = xs.iter();

        while let Ok(x) = rx.try_recv() {
            if let Some(y) = xs.next() {
                assert_eq!(x, *y)
            } else {
                panic!("assert_receiver_eq: never nexted `{:?}`", x)
            }
        }

        if let Some(x) = xs.next() {
            panic!("assert_receiver_eq: never received `{:?}`", x)
        }

    }

    #[test]
    fn test_install_package_update_0() {

        let (tx, rx) = channel();

        assert_eq!(install_package_update(
            &Config::default(),
            &mut TestHttpClient::new(),
            &AccessToken::default(),
            &"0".to_string(),
            &tx).unwrap().operation_results.pop().unwrap().result_code,
                   UpdateResultCode::GENERAL_ERROR);

        assert_receiver_eq(rx, &[
            Event::UpdateErrored("0".to_string(), String::from(
                "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download\")"))])

    }

    #[test]
    fn test_install_package_update_1() {

        let mut config = Config::default();

        config.ota.packages_dir    = "/tmp/".to_string();
        config.ota.package_manager = PackageManager::File {
            filename: "test_install_package_update_1".to_string(),
            succeeds: false };

        let (tx, rx) = channel();

        assert_eq!(install_package_update(
            &config,
            &mut TestHttpClient::from(vec![""]),
            &AccessToken::default(),
            &"0".to_string(),
            &tx).unwrap().operation_results.pop().unwrap().result_code,
                   UpdateResultCode::INSTALL_FAILED);

        assert_receiver_eq(rx, &[
            Event::UpdateStateChanged("0".to_string(), UpdateState::Installing),
            // XXX: Not very helpful message?
            Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"failed\"".to_string())])
    }

    #[test]
    fn test_install_package_update_2() {

        let mut config = Config::default();

        config.ota.packages_dir    = "/tmp/".to_string();
        config.ota.package_manager = PackageManager::File {
            filename: "test_install_package_update_2".to_string(),
            succeeds: true };

        let (tx, rx) = channel();

        assert_eq!(install_package_update(
            &config,
            &mut TestHttpClient::from(vec!["", ""]),
            &AccessToken::default(),
            &"0".to_string(),
            &tx).unwrap().operation_results.pop().unwrap().result_code,
                   UpdateResultCode::OK);

        assert_receiver_eq(rx, &[
            Event::UpdateStateChanged("0".to_string(), UpdateState::Installing),
            Event::UpdateStateChanged("0".to_string(), UpdateState::Installed)])

    }
}
<commit_msg>Remove extra slash.<commit_after>use rustc_serialize::json;
use std::fs::File;
use std::path::PathBuf;
use std::sync::mpsc::Sender;

use datatype::{AccessToken, Config, Event, Error, Url, UpdateRequestId,
               UpdateReport, UpdateReportWithVin, Package,
               UpdateResultCode, UpdateState, PendingUpdateRequest};

use http_client::{Auth, HttpClient, HttpRequest};

fn vehicle_updates_endpoint(config: &Config, path: &str) -> Url {
    config.ota.server.join(& if path.is_empty() {
        format!("/api/v1/vehicle_updates/{}", config.auth.vin)
    } else {
        format!("/api/v1/vehicle_updates/{}/{}", config.auth.vin, path)
    }).unwrap()
}

pub fn download_package_update(config: &Config,
                               client: &mut HttpClient,
                               token:  &AccessToken,
                               id:     &UpdateRequestId) -> Result<PathBuf, Error> {

    let req = HttpRequest::get(
        vehicle_updates_endpoint(config, &format!("{}/download", id)),
        Some(Auth::Token(token)),
    );

    let mut path = PathBuf::new();
    path.push(&config.ota.packages_dir);
    path.push(id);
    path.set_extension(config.ota.package_manager.extension());

    let mut file = try!(File::create(path.as_path()));

    try!(client.send_request_to(&req, &mut file));

    return Ok(path)

}

pub fn send_install_report(config: &Config,
                           client: &mut HttpClient,
                           token:  &AccessToken,
                           report: &UpdateReport) -> Result<(), Error> {

    let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report);
    let json            = try!(json::encode(&report_with_vin));

    let req = HttpRequest::post(
        vehicle_updates_endpoint(config, &format!("{}", report.update_id)),
        Some(Auth::Token(token)),
        Some(json)
    );

    let _: String = try!(client.send_request(&req));

    return Ok(())

}

pub fn get_package_updates(config: &Config,
                           client: &mut HttpClient,
                           token:  &AccessToken) -> Result<Vec<PendingUpdateRequest>, Error> {

    let req = HttpRequest::get(
        vehicle_updates_endpoint(&config, ""),
        Some(Auth::Token(token)),
    );

    let resp = try!(client.send_request(&req));

    return Ok(try!(json::decode::<Vec<PendingUpdateRequest>>(&resp)));
}

// XXX: Remove in favour of update_installed_packages()?
pub fn update_packages(config: &Config,
                     client: &mut HttpClient,
                     token:  &AccessToken,
                     pkgs:   &Vec<Package>) -> Result<(), Error> {
    let json = try!(json::encode(&pkgs));

    let req = HttpRequest::put(
        vehicle_updates_endpoint(config, "installed"),
        Some(Auth::Token(token)),
        Some(json),
    );

    let _: String = try!(client.send_request(&req));

    return Ok(())
}

pub fn update_installed_packages(config: &Config,
                               client: &mut HttpClient,
                               token:  &AccessToken) -> Result<(), Error> {

    let pkgs = try!(config.ota.package_manager.installed_packages());
    update_packages(config, client, token, &pkgs)

}

pub fn install_package_update(config:      &Config,
                              http_client: &mut HttpClient,
                              token:       &AccessToken,
                              id:          &UpdateRequestId,
                              tx:          &Sender<Event>) -> Result<UpdateReport, Error> {

    match download_package_update(config, http_client, token, id) {

        Ok(path) => {
            info!("Downloaded at {:?}. Installing...", path);
            try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)));

            let p = try!(path.to_str()
                         .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path))));

            match config.ota.package_manager.install_package(p) {

                Ok((code, output)) => {
                    try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)));
                    try!(update_installed_packages(config, http_client, token));
                    Ok(UpdateReport::new(id.clone(), code, output))
                }

                Err((code, output)) => {
                    try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output))));
                    Ok(UpdateReport::new(id.clone(), code, output))
                }

            }

        }

        Err(err) => {
            try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err))));
            Ok(UpdateReport::new(id.clone(),
                              UpdateResultCode::GENERAL_ERROR,
                              format!("Download failed: {:?}", err)))
        }
    }

}

#[cfg(test)]
mod tests {

    use std::fmt::Debug;
    use std::sync::mpsc::{channel, Receiver};
    use rustc_serialize::json;

    use super::*;
    use datatype::{AccessToken, Config, Event, OtaConfig, Package,
                   UpdateResultCode, UpdateState, PendingUpdateRequest};
    use http_client::TestHttpClient;
    use package_manager::PackageManager;

    fn test_token() -> AccessToken {
        AccessToken {
            access_token: "token".to_string(),
            token_type: "bar".to_string(),
            expires_in: 20,
            scope: vec![]
        }
    }

    fn test_package() -> Package {
        Package {
            name: "hey".to_string(),
            version: "1.2.3".to_string()
        }
    }

    #[test]
    fn test_update_packages_sends_authentication() {
        assert_eq!(update_packages(&Config::default(),
                                 &mut TestHttpClient::from(vec![""]),
                                 &test_token(),
                                 &vec![test_package()])
                   .unwrap(), ())
    }

    #[test]
    fn test_get_package_updates() {
        let pending_update = PendingUpdateRequest {
            id: "someid".to_string(),
            installPos: 0,
            packageId: Package {
                name: "fake-pkg".to_string(),
                version: "0.1.1".to_string()
            },
            createdAt: "2010-01-01".to_string()
        };

        let json_response = format!("[{}]",json::encode(&pending_update).unwrap());

        let updates: Vec<PendingUpdateRequest> = get_package_updates(&Config::default(),
                                       &mut TestHttpClient::from(vec![json_response.as_str()]),
                                       &test_token()).unwrap();

        let update_ids: Vec<String> = updates.iter().map(|p| p.id.clone()).collect();

        assert_eq!(update_ids, vec!["someid".to_string()])
    }

    #[test]
    #[ignore] // TODO: docker daemon requires user namespaces for this to work
    fn bad_packages_dir_download_package_update() {
        let mut config = Config::default();
        config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota };

        assert_eq!(format!("{}", download_package_update(&config,
                                                         &mut TestHttpClient::from(vec![""]),
                                                         &test_token(),
                                                         &"0".to_string())
                           .unwrap_err()),
                   "IO error: Permission denied (os error 13)")
    }

    #[test]
    fn bad_client_download_package_update() {
        assert_eq!(format!("{}",
                           download_package_update(&Config::default(),
                                                   &mut TestHttpClient::new(),
                                                   &test_token(),
                                                   &"0".to_string())
                           .unwrap_err()),
                   "Http client error: GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download")
    }

    fn assert_receiver_eq<X: PartialEq + Debug>(rx: Receiver<X>, xs: &[X]) {

        let mut xs = xs.iter();

        while let Ok(x) = rx.try_recv() {
            if let Some(y) = xs.next() {
                assert_eq!(x, *y)
            } else {
                panic!("assert_receiver_eq: never nexted `{:?}`", x)
            }
        }

        if let Some(x) = xs.next() {
            panic!("assert_receiver_eq: never received `{:?}`", x)
        }

    }

    #[test]
    fn test_install_package_update_0() {

        let (tx, rx) = channel();

        assert_eq!(install_package_update(
            &Config::default(),
            &mut TestHttpClient::new(),
            &AccessToken::default(),
            &"0".to_string(),
            &tx).unwrap().operation_results.pop().unwrap().result_code,
                   UpdateResultCode::GENERAL_ERROR);

        assert_receiver_eq(rx, &[
            Event::UpdateErrored("0".to_string(), String::from(
                "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download\")"))])

    }

    #[test]
    fn test_install_package_update_1() {

        let mut config = Config::default();

        config.ota.packages_dir    = "/tmp/".to_string();
        config.ota.package_manager = PackageManager::File {
            filename: "test_install_package_update_1".to_string(),
            succeeds: false };

        let (tx, rx) = channel();

        assert_eq!(install_package_update(
            &config,
            &mut TestHttpClient::from(vec![""]),
            &AccessToken::default(),
            &"0".to_string(),
            &tx).unwrap().operation_results.pop().unwrap().result_code,
                   UpdateResultCode::INSTALL_FAILED);

        assert_receiver_eq(rx, &[
            Event::UpdateStateChanged("0".to_string(), UpdateState::Installing),
            // XXX: Not very helpful message?
            Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"failed\"".to_string())])
    }

    #[test]
    fn test_install_package_update_2() {

        let mut config = Config::default();

        config.ota.packages_dir    = "/tmp/".to_string();
        config.ota.package_manager = PackageManager::File {
            filename: "test_install_package_update_2".to_string(),
            succeeds: true };

        let (tx, rx) = channel();

        assert_eq!(install_package_update(
            &config,
            &mut TestHttpClient::from(vec!["", ""]),
            &AccessToken::default(),
            &"0".to_string(),
            &tx).unwrap().operation_results.pop().unwrap().result_code,
                   UpdateResultCode::OK);

        assert_receiver_eq(rx, &[
            Event::UpdateStateChanged("0".to_string(), UpdateState::Installing),
            Event::UpdateStateChanged("0".to_string(), UpdateState::Installed)])

    }
}
<|endoftext|>


<commit_before>// Copyright 2015 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.

use Indent;
use rewrite::{Rewrite, RewriteContext};
use utils::{wrap_str, format_mutability, span_after};
use lists::{format_item_list, itemize_list};
use expr::{rewrite_unary_prefix, rewrite_pair, rewrite_tuple};
use types::rewrite_path;

use syntax::ast::{BindingMode, Pat, Pat_, FieldPat};

impl Rewrite for Pat {
    fn rewrite(&self, context: &RewriteContext, width: usize, offset: Indent) -> Option<String> {
        match self.node {
            Pat_::PatBox(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, width, offset),
            Pat_::PatIdent(binding_mode, ident, ref sub_pat) => {
                let (prefix, mutability) = match binding_mode {
                    BindingMode::BindByRef(mutability) => ("ref ", mutability),
                    BindingMode::BindByValue(mutability) => ("", mutability),
                };
                let mut_infix = format_mutability(mutability);
                let id_str = ident.node.to_string();

                let sub_pat = match *sub_pat {
                    Some(ref p) => {
                        let width = try_opt!(width.checked_sub(prefix.len() + mut_infix.len() +
                                                               id_str.len()));
                        format!(" @ {}", try_opt!(p.rewrite(context, width, offset)))
                    }
                    None => "".to_owned(),
                };

                let result = format!("{}{}{}{}", prefix, mut_infix, id_str, sub_pat);
                wrap_str(result, context.config.max_width, width, offset)
            }
            Pat_::PatWild => {
                if 1 <= width {
                    Some("_".to_owned())
                } else {
                    None
                }
            }
            Pat_::PatQPath(ref q_self, ref path) => {
                rewrite_path(context, true, Some(q_self), path, width, offset)
            }
            Pat_::PatRange(ref lhs, ref rhs) => {
                rewrite_pair(&**lhs, &**rhs, "", "...", "", context, width, offset)
            }
            Pat_::PatRegion(ref pat, mutability) => {
                let prefix = format!("&{}", format_mutability(mutability));
                rewrite_unary_prefix(context, &prefix, &**pat, width, offset)
            }
            Pat_::PatTup(ref items) => {
                rewrite_tuple(context,
                              items.iter().map(|x| &**x),
                              self.span,
                              width,
                              offset)
            }
            Pat_::PatEnum(ref path, ref pat_vec) => {
                let path_str = try_opt!(rewrite_path(context, true, None, path, width, offset));

                match *pat_vec {
                    Some(ref pat_vec) => {
                        if pat_vec.is_empty() {
                            Some(path_str)
                        } else {
                            // 1 = (
                            let width = try_opt!(width.checked_sub(path_str.len() + 1));
                            let offset = offset + path_str.len() + 1;
                            let items = itemize_list(context.codemap,
                                                     pat_vec.iter(),
                                                     ")",
                                                     |item| item.span.lo,
                                                     |item| item.span.hi,
                                                     |item| item.rewrite(context, width, offset),
                                                     span_after(self.span, "(", context.codemap),
                                                     self.span.hi);
                            Some(format!("{}({})",
                                         path_str,
                                         try_opt!(format_item_list(items,
                                                                   width,
                                                                   offset,
                                                                   context.config))))
                        }
                    }
                    None => Some(format!("{}(..)", path_str)),
                }
            }
            Pat_::PatLit(ref expr) => expr.rewrite(context, width, offset),
            Pat_::PatVec(ref prefix, ref slice_pat, ref suffix) => {
                // Rewrite all the sub-patterns.
                let prefix = prefix.iter().map(|p| p.rewrite(context, width, offset));
                let slice_pat = slice_pat.as_ref().map(|p| {
                    Some(format!("{}..", try_opt!(p.rewrite(context, width, offset))))
                });
                let suffix = suffix.iter().map(|p| p.rewrite(context, width, offset));

                // Munge them together.
                let pats = prefix.chain(slice_pat.into_iter()).chain(suffix);

                // Check that all the rewrites succeeded, and if not return None.
                let (somes, nones) = pats.partition::<Vec<Option<String>>, _>(Option::is_some);
                if nones.len() > 0 {
                    return None;
                }

                // Unwrap all the sub-strings and join them with commas.
                let pats = somes.into_iter().map(|p| p.unwrap()).collect::<Vec<_>>().join(", ");
                Some(format!("[{}]", pats))
            }
            Pat_::PatStruct(ref path, ref fields, elipses) => {
                let path = try_opt!(rewrite_path(context, true, None, path, width, offset));

                let (elipses_str, terminator) = if elipses {
                    (", ..", "..")
                } else {
                    ("", "}")
                };

                let budget = try_opt!(width.checked_sub(path.len() + 5 + elipses_str.len()));
                // FIXME Using visual indenting, should use block or visual to match
                // struct lit preference (however, in practice I think it is rare
                // for struct patterns to be multi-line).
                let offset = offset + path.len() + 3;

                let items = itemize_list(context.codemap,
                                         fields.iter(),
                                         terminator,
                                         |f| f.span.lo,
                                         |f| f.span.hi,
                                         |f| f.node.rewrite(context, budget, offset),
                                         span_after(self.span, "{", context.codemap),
                                         self.span.hi);
                let mut field_string = try_opt!(format_item_list(items,
                                                                 budget,
                                                                 offset,
                                                                 context.config));
                if elipses {
                    if field_string.contains('\n') {
                        field_string.push_str(",\n");
                        field_string.push_str(&offset.to_string(context.config));
                        field_string.push_str("..");
                    } else {
                        if field_string.len() > 0 {
                            field_string.push_str(", ");
                        }
                        field_string.push_str("..");
                    }
                }

                if field_string.len() == 0 {
                    Some(format!("{} {{}}", path))
                } else {
                    Some(format!("{} {{ {} }}", path, field_string))
                }
            }
            // FIXME(#819) format pattern macros.
            Pat_::PatMac(..) => {
                wrap_str(context.snippet(self.span),
                         context.config.max_width,
                         width,
                         offset)
            }
        }
    }
}

impl Rewrite for FieldPat {
    fn rewrite(&self, context: &RewriteContext, width: usize, offset: Indent) -> Option<String> {
        let pat = self.pat.rewrite(context, width, offset);
        if self.is_shorthand {
            pat
        } else {
            Some(format!("{}: {}", self.ident.to_string(), try_opt!(pat)))
        }
    }
}
<commit_msg>reviewer changes<commit_after>// Copyright 2015 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.

use Indent;
use rewrite::{Rewrite, RewriteContext};
use utils::{wrap_str, format_mutability, span_after};
use lists::{format_item_list, itemize_list};
use expr::{rewrite_unary_prefix, rewrite_pair, rewrite_tuple};
use types::rewrite_path;

use syntax::ast::{BindingMode, Pat, Pat_, FieldPat};

impl Rewrite for Pat {
    fn rewrite(&self, context: &RewriteContext, width: usize, offset: Indent) -> Option<String> {
        match self.node {
            Pat_::PatBox(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, width, offset),
            Pat_::PatIdent(binding_mode, ident, ref sub_pat) => {
                let (prefix, mutability) = match binding_mode {
                    BindingMode::BindByRef(mutability) => ("ref ", mutability),
                    BindingMode::BindByValue(mutability) => ("", mutability),
                };
                let mut_infix = format_mutability(mutability);
                let id_str = ident.node.to_string();

                let sub_pat = match *sub_pat {
                    Some(ref p) => {
                        // 3 - ` @ `.
                        let width = try_opt!(width.checked_sub(prefix.len() + mut_infix.len() +
                                                               id_str.len() +
                                                               3));
                        format!(" @ {}", try_opt!(p.rewrite(context, width, offset)))
                    }
                    None => "".to_owned(),
                };

                let result = format!("{}{}{}{}", prefix, mut_infix, id_str, sub_pat);
                wrap_str(result, context.config.max_width, width, offset)
            }
            Pat_::PatWild => {
                if 1 <= width {
                    Some("_".to_owned())
                } else {
                    None
                }
            }
            Pat_::PatQPath(ref q_self, ref path) => {
                rewrite_path(context, true, Some(q_self), path, width, offset)
            }
            Pat_::PatRange(ref lhs, ref rhs) => {
                rewrite_pair(&**lhs, &**rhs, "", "...", "", context, width, offset)
            }
            Pat_::PatRegion(ref pat, mutability) => {
                let prefix = format!("&{}", format_mutability(mutability));
                rewrite_unary_prefix(context, &prefix, &**pat, width, offset)
            }
            Pat_::PatTup(ref items) => {
                rewrite_tuple(context,
                              items.iter().map(|x| &**x),
                              self.span,
                              width,
                              offset)
            }
            Pat_::PatEnum(ref path, ref pat_vec) => {
                let path_str = try_opt!(rewrite_path(context, true, None, path, width, offset));

                match *pat_vec {
                    Some(ref pat_vec) => {
                        if pat_vec.is_empty() {
                            Some(path_str)
                        } else {
                            // 1 = (
                            let width = try_opt!(width.checked_sub(path_str.len() + 1));
                            let offset = offset + path_str.len() + 1;
                            let items = itemize_list(context.codemap,
                                                     pat_vec.iter(),
                                                     ")",
                                                     |item| item.span.lo,
                                                     |item| item.span.hi,
                                                     |item| item.rewrite(context, width, offset),
                                                     span_after(self.span, "(", context.codemap),
                                                     self.span.hi);
                            Some(format!("{}({})",
                                         path_str,
                                         try_opt!(format_item_list(items,
                                                                   width,
                                                                   offset,
                                                                   context.config))))
                        }
                    }
                    None => Some(format!("{}(..)", path_str)),
                }
            }
            Pat_::PatLit(ref expr) => expr.rewrite(context, width, offset),
            Pat_::PatVec(ref prefix, ref slice_pat, ref suffix) => {
                // Rewrite all the sub-patterns.
                let prefix = prefix.iter().map(|p| p.rewrite(context, width, offset));
                let slice_pat = slice_pat.as_ref().map(|p| {
                    Some(format!("{}..", try_opt!(p.rewrite(context, width, offset))))
                });
                let suffix = suffix.iter().map(|p| p.rewrite(context, width, offset));

                // Munge them together.
                let pats: Option<Vec<String>> = prefix.chain(slice_pat.into_iter())
                                                      .chain(suffix)
                                                      .collect();

                // Check that all the rewrites succeeded, and if not return None.
                let pats = try_opt!(pats);

                // Unwrap all the sub-strings and join them with commas.
                let result = format!("[{}]", pats.join(", "));
                wrap_str(result, context.config.max_width, width, offset)
            }
            Pat_::PatStruct(ref path, ref fields, elipses) => {
                let path = try_opt!(rewrite_path(context, true, None, path, width, offset));

                let (elipses_str, terminator) = if elipses {
                    (", ..", "..")
                } else {
                    ("", "}")
                };

                // 5 = `{` plus space before and after plus `}` plus space before.
                let budget = try_opt!(width.checked_sub(path.len() + 5 + elipses_str.len()));
                // FIXME Using visual indenting, should use block or visual to match
                // struct lit preference (however, in practice I think it is rare
                // for struct patterns to be multi-line).
                // 3 = `{` plus space before and after.
                let offset = offset + path.len() + 3;

                let items = itemize_list(context.codemap,
                                         fields.iter(),
                                         terminator,
                                         |f| f.span.lo,
                                         |f| f.span.hi,
                                         |f| f.node.rewrite(context, budget, offset),
                                         span_after(self.span, "{", context.codemap),
                                         self.span.hi);
                let mut field_string = try_opt!(format_item_list(items,
                                                                 budget,
                                                                 offset,
                                                                 context.config));
                if elipses {
                    if field_string.contains('\n') {
                        field_string.push_str(",\n");
                        field_string.push_str(&offset.to_string(context.config));
                        field_string.push_str("..");
                    } else {
                        if field_string.len() > 0 {
                            field_string.push_str(", ");
                        }
                        field_string.push_str("..");
                    }
                }

                if field_string.is_empty() {
                    Some(format!("{} {{}}", path))
                } else {
                    Some(format!("{} {{ {} }}", path, field_string))
                }
            }
            // FIXME(#819) format pattern macros.
            Pat_::PatMac(..) => {
                wrap_str(context.snippet(self.span),
                         context.config.max_width,
                         width,
                         offset)
            }
        }
    }
}

impl Rewrite for FieldPat {
    fn rewrite(&self, context: &RewriteContext, width: usize, offset: Indent) -> Option<String> {
        let pat = self.pat.rewrite(context, width, offset);
        if self.is_shorthand {
            pat
        } else {
            wrap_str(format!("{}: {}", self.ident.to_string(), try_opt!(pat)),
                     context.config.max_width,
                     width,
                     offset)
        }
    }
}
<|endoftext|>