geolite 0.0.18

open source geocode solution
use serde::Serialize;
use tiny_http::{Header, Request};

use crate::address::{address_id, query_opts};

#[derive(Serialize)]
struct question<'a> {
  version: &'static str,
  input: &'a str,
  friendly_name_format: Option<&'a str>,
  min_quality: Option<f64>,
  bounding_wkt: Option<&'a str>,
  last_admin_levels: Option<Vec<u8>>,
  country: Option<Vec<String>>,
}

#[derive(Serialize)]
struct address_question {
  version: &'static str,
  id: String,
}

pub fn of_question(input: &str, opts: &query_opts<'_>, country: Option<&[String]>) -> String {
  let query_opts {
    friendly_name_format,
    min_quality,
    bounding,
    last_admin_levels,
  } = opts;
  let question = question {
    version: env!("CARGO_PKG_VERSION"),
    input,
    friendly_name_format: *friendly_name_format,
    min_quality: *min_quality,
    bounding_wkt: bounding.as_ref().map(|bounding| bounding.wkt.as_str()),
    last_admin_levels: last_admin_levels
      .as_ref()
      .map(|levels| levels.iter().map(|level| level.value()).collect()),
    country: country.map(|isos| isos.iter().map(|iso| iso.to_ascii_lowercase()).collect()),
  };
  of_body(&serde_json::to_string(&question).expect("a question serializes"))
}

pub fn of_address(id: &address_id) -> String {
  let question = address_question {
    version: env!("CARGO_PKG_VERSION"),
    id: id.to_string(),
  };
  of_body(&serde_json::to_string(&question).expect("an address question serializes"))
}

pub fn of_body(body: &str) -> String {
  format!("\"{:x}\"", md5::compute(body))
}

pub fn header(etag: &str) -> Header {
  Header::from_bytes("ETag", etag).unwrap()
}

pub fn none_match(request: &Request, etag: &str) -> bool {
  request
    .headers()
    .iter()
    .filter(|header| header.field.equiv("If-None-Match"))
    .flat_map(|header| header.value.as_str().split(','))
    .map(str::trim)
    .any(|tag| tag == "*" || opaque(tag) == etag)
}

fn opaque(tag: &str) -> &str {
  tag.strip_prefix("W/").unwrap_or(tag)
}