1
1
use anyhow:: { anyhow, Result } ;
2
- use reqwest:: { header :: USER_AGENT , Client , Url } ;
2
+ use reqwest:: { Client , Url } ;
3
3
4
- use serde:: { Deserialize , Serialize } ;
4
+ use serde:: Deserialize ;
5
5
6
- // Geoip json
7
- #[ derive( Serialize , Deserialize , Debug ) ]
8
- pub struct Geolocation {
6
+ #[ derive( Deserialize ) ]
7
+ pub struct Address {
8
+ pub name : String ,
9
+ pub lat : String ,
10
+ pub lon : String ,
11
+ }
12
+
13
+ #[ derive( Deserialize ) ]
14
+ pub struct GeoIpLocation {
9
15
pub latitude : f64 ,
10
16
pub longitude : f64 ,
11
17
pub city_name : String ,
12
18
pub country_code : String ,
13
19
}
14
20
15
- // Open street map(OSM) json
16
- #[ derive( Serialize , Deserialize , Debug , Clone ) ]
17
- pub struct Address {
18
- place_id : u64 ,
19
- licence : String ,
20
- osm_type : String ,
21
- osm_id : u64 ,
22
- boundingbox : Vec < String > ,
23
- pub lat : String ,
24
- pub lon : String ,
25
- pub display_name : String ,
26
- class : String ,
27
- #[ serde( rename( deserialize = "type" ) ) ]
28
- kind : String ,
29
- importance : f64 ,
21
+ #[ derive( Deserialize ) ]
22
+ struct OpenStreetMapGeoObj {
23
+ // place_id: u64,
24
+ // licence: String,
25
+ // osm_type: String,
26
+ // osm_id: u64,
27
+ // boundingbox: Vec<String>,
28
+ lat : String ,
29
+ lon : String ,
30
+ display_name : String ,
31
+ // place_rank: i32,
32
+ // category: String,
33
+ // #[serde(rename(deserialize = "type"))]
34
+ // kind: String,
35
+ // importance: f64,
36
+ // icon: String,
37
+ }
38
+
39
+ #[ derive( Deserialize ) ]
40
+ struct OpenMeteoResults {
41
+ results : Vec < OpenMeteoGeoObj > ,
30
42
}
31
43
32
- impl Geolocation {
33
- pub async fn get ( ) -> Result < Geolocation > {
44
+ #[ derive( Deserialize ) ]
45
+ struct OpenMeteoGeoObj {
46
+ // id: i32,
47
+ name : String ,
48
+ latitude : f64 ,
49
+ longitude : f64 ,
50
+ // elevation: f64,
51
+ // timezone: String,
52
+ // feature_code: String,
53
+ // country_code: String,
54
+ // country: String,
55
+ // country_id: i32,
56
+ // population: i32,
57
+ // admin1: String,
58
+ // admin2: String,
59
+ // admin3: String,
60
+ // admin4: String,
61
+ // admin1_id: i32,
62
+ // admin2_id: i32,
63
+ // admin3_id: i32,
64
+ // admin4_id: i32,
65
+ // postcodes: Vec<String>,
66
+ }
67
+
68
+ impl GeoIpLocation {
69
+ pub async fn get ( ) -> Result < GeoIpLocation > {
34
70
let url = Url :: parse ( "https://api.geoip.rs" ) ?;
35
71
36
- let res = reqwest:: get ( url) . await ?. json :: < Geolocation > ( ) . await ?;
72
+ let res = reqwest:: get ( url) . await ?. json :: < GeoIpLocation > ( ) . await ?;
37
73
38
74
Ok ( res)
39
75
}
40
76
41
- pub async fn search ( address : & str , lang : & str ) -> Result < Address > {
77
+ async fn search_osm ( client : & Client , address : & str , lang : & str ) -> Result < Address > {
42
78
let url = format ! (
43
- "https://nominatim.openstreetmap.org/search?q={address}&accept-language={lang}&limit=1&format=json"
79
+ "https://nominatim.openstreetmap.org/search?q={address}&accept-language={lang}&limit=1&format=jsonv2" ,
44
80
) ;
81
+ let results: Vec < OpenStreetMapGeoObj > = client. get ( & url) . send ( ) . await ?. json ( ) . await ?;
82
+ let result = results. first ( ) . ok_or_else ( || anyhow ! ( "Location request failed." ) ) ?;
45
83
46
- let res = Client :: new ( )
47
- . get ( & url)
48
- . header ( USER_AGENT , "wthrr-the-weathercrab" )
49
- . send ( )
50
- . await ?
51
- . json :: < Vec < Address > > ( )
52
- . await ?;
84
+ Ok ( Address {
85
+ name : result. display_name . clone ( ) ,
86
+ lon : result. lon . to_string ( ) ,
87
+ lat : result. lat . to_string ( ) ,
88
+ } )
89
+ }
53
90
54
- if res. is_empty ( ) {
55
- return Err ( anyhow ! ( "Location request failed." ) ) ;
56
- }
91
+ async fn search_open_meteo ( client : & Client ) -> Result < Address > {
92
+ let url = "https://geocoding-api.open-meteo.com/v1/search?name=Berlin&language=fr" ;
93
+ let results: OpenMeteoResults = client. get ( url) . send ( ) . await ?. json ( ) . await ?;
94
+ let result = results
95
+ . results
96
+ . first ( )
97
+ . ok_or_else ( || anyhow ! ( "Location request failed." ) ) ?;
57
98
58
- Ok ( res[ 0 ] . clone ( ) )
99
+ Ok ( Address {
100
+ name : result. name . clone ( ) ,
101
+ lon : result. longitude . to_string ( ) ,
102
+ lat : result. latitude . to_string ( ) ,
103
+ } )
104
+ }
105
+
106
+ pub async fn search ( address : & str , lang : & str ) -> Result < Address > {
107
+ let client = Client :: builder ( ) . user_agent ( "wthrr-the-weathercrab" ) . build ( ) ?;
108
+ let results = Self :: search_osm ( & client, address, lang) . await ;
109
+
110
+ match results {
111
+ Ok ( address) => Ok ( address) ,
112
+ Err ( _) => Self :: search_open_meteo ( & client) . await ,
113
+ }
59
114
}
60
115
}
61
116
@@ -67,11 +122,11 @@ mod tests {
67
122
async fn geolocation_response ( ) -> Result < ( ) > {
68
123
let ( address, lang_de, lang_pl) = ( "berlin" , "de" , "pl" ) ;
69
124
70
- let loc_de = Geolocation :: search ( address, lang_de) . await ?;
71
- let loc_pl = Geolocation :: search ( address, lang_pl) . await ?;
125
+ let loc_de = GeoIpLocation :: search ( address, lang_de) . await ?;
126
+ let loc_pl = GeoIpLocation :: search ( address, lang_pl) . await ?;
72
127
73
- assert ! ( loc_de. display_name . contains( "Deutschland" ) ) ;
74
- assert ! ( loc_pl. display_name . contains( "Niemcy" ) ) ;
128
+ assert ! ( loc_de. name . contains( "Deutschland" ) ) ;
129
+ assert ! ( loc_pl. name . contains( "Niemcy" ) ) ;
75
130
76
131
Ok ( ( ) )
77
132
}
0 commit comments