daummap 0.4.0

Kakao Map API wrapper
Documentation
use {
    crate::{request, Address, LandLotAddress, RoadAddress, KAKAO_LOCAL_API_BASE_URL},
    serde::{de::DeserializeOwned, Deserialize},
};

#[derive(Debug, Clone)]
pub struct CoordRequest {
    base_url: String,
    app_key: String,
    page: usize,
    longitude: f32,
    latitude: f32,
}

impl CoordRequest {
    pub fn new(app_key: &str, longitude: f32, latitude: f32) -> Self {
        CoordRequest {
            base_url: KAKAO_LOCAL_API_BASE_URL.to_string(),
            app_key: app_key.to_string(),
            page: 1,
            longitude,
            latitude,
        }
    }

    pub fn base_url(&mut self, base_url: &str) -> &mut Self {
        self.base_url = base_url.to_string();
        self
    }

    pub fn page(&mut self, page: usize) -> &mut Self {
        self.page = page;
        self
    }

    async fn request<T: DeserializeOwned>(&self, api_path: &str) -> Result<T, failure::Error> {
        request::<T>(
            &self.base_url,
            api_path,
            &[
                ("page", self.page.to_string()),
                ("x", self.longitude.to_string()),
                ("y", self.latitude.to_string()),
            ],
            &self.app_key,
        )
        .await
    }

    pub async fn get_region(&self) -> Result<Vec<Region>, failure::Error> {
        static API_PATH: &'static str = "/geo/coord2regioncode.json";

        let resp = self.request::<Coord2RegionResponse>(API_PATH).await?;
        Ok(resp.documents.into_iter().map(Into::into).collect())
    }

    pub async fn get_address(&self) -> Result<Vec<Address>, failure::Error> {
        static API_PATH: &'static str = "/geo/coord2address.json";

        let resp = self.request::<Coord2AddressResponse>(API_PATH).await?;
        Ok(resp
            .documents
            .into_iter()
            .map(|document| Address {
                address: None,
                land_lot: document.address.map(Into::into),
                road: document.road_address.map(Into::into),
            })
            .collect())
    }
}

#[derive(Debug, Deserialize)]
struct Coord2AddressResponse {
    documents: Vec<Coord2AddressDocument>,
}

#[derive(Debug, Deserialize)]
struct Coord2RegionResponse {
    documents: Vec<RawRegion>,
}

#[derive(Debug, Deserialize)]
struct Coord2AddressDocument {
    address: Option<RawLandLotAddress>,
    road_address: Option<RawRoadAddress>,
}

#[derive(Debug, Deserialize)]
struct RawRegion {
    address_name: String,
    region_1depth_name: String,
    region_2depth_name: String,
    region_3depth_name: String,
    region_4depth_name: String,
    code: String,
    x: f32,
    y: f32,
}

#[derive(Debug, Clone)]
pub struct Region {
    pub address: String,
    pub province: String,
    pub city: String,
    pub town: String,
    pub neighborhood: String,
    pub code: Option<usize>,
    pub longitude: Option<f32>,
    pub latitude: Option<f32>,
}

#[derive(Debug, Deserialize)]
struct RawLandLotAddress {
    address_name: String,
    region_1depth_name: String,
    region_2depth_name: String,
    region_3depth_name: String,
    mountain_yn: String,
    main_address_no: String,
    sub_address_no: String,
    zip_code: String,
}

#[derive(Debug, Deserialize)]
struct RawRoadAddress {
    address_name: String,
    region_1depth_name: String,
    region_2depth_name: String,
    region_3depth_name: String,
    road_name: String,
    underground_yn: String,
    main_building_no: String,
    sub_building_no: String,
    building_name: String,
    zone_no: String,
}

impl From<RawRegion> for Region {
    fn from(rreg: RawRegion) -> Self {
        Region {
            address: rreg.address_name,
            province: rreg.region_1depth_name,
            city: rreg.region_2depth_name,
            town: rreg.region_3depth_name,
            neighborhood: rreg.region_4depth_name,
            code: rreg.code.parse::<usize>().ok(),
            longitude: Some(rreg.x),
            latitude: Some(rreg.y),
        }
    }
}

impl Into<LandLotAddress> for RawLandLotAddress {
    fn into(self) -> LandLotAddress {
        LandLotAddress {
            address: self.address_name,
            province: self.region_1depth_name,
            city: self.region_2depth_name,
            town: self.region_3depth_name,
            neighborhood: None,
            h_code: None,
            b_code: None,
            is_mountain: if self.mountain_yn.is_empty() {
                None
            } else {
                Some(self.mountain_yn == "Y")
            },
            main_address_number: self.main_address_no.parse::<usize>().ok(),
            sub_address_number: self.sub_address_no.parse::<usize>().ok(),
            zip_code: self.zip_code.parse::<usize>().ok(),
            longitude: None,
            latitude: None,
        }
    }
}

impl Into<RoadAddress> for RawRoadAddress {
    fn into(self) -> RoadAddress {
        RoadAddress {
            address: self.address_name,
            province: self.region_1depth_name,
            city: self.region_2depth_name,
            town: self.region_3depth_name,
            road_name: self.road_name,
            is_underground: self.underground_yn == "Y",
            main_building_number: self.main_building_no.parse::<usize>().ok(),
            sub_building_number: self.sub_building_no.parse::<usize>().ok(),
            building_name: self.building_name,
            post_code: self.zone_no.parse::<usize>().ok(),
            longitude: None,
            latitude: None,
        }
    }
}