-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a geolocator based by the IP-API free tier
Because of the limitations of the free tier, this new geolocator is backed by another geolocator that can provide answers when the free tier quota is exhausted. To make this possible, this change adds a new SettableClock fake clock for testing purposes, which is much nicer to use than the older MonotonicClock. Additionally, this change also adds a RequestCounter to keep track of how many requests have been sent. I'm keeping this in the geo crate for now, but this type of request counter should likely be placed in the core crate; will do so when the need arises.
- Loading branch information
Showing
5 changed files
with
432 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// III-IV | ||
// Copyright 2023 Julio Merino | ||
// | ||
// 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. | ||
|
||
//! Counter of requests for a period of time. | ||
|
||
use iii_iv_core::clocks::Clock; | ||
use std::{sync::Arc, time::Duration}; | ||
|
||
/// Counts the number of requests over the last minute with second resolution. | ||
pub(crate) struct RequestCounter { | ||
/// Clock to obtain the current time from. | ||
clock: Arc<dyn Clock + Send + Sync>, | ||
|
||
/// Tracker of per-second counts within a minute. | ||
/// | ||
/// Each pair contains the timestamp of the ith second in the array and | ||
/// the counter of requests at that second. | ||
counts: [(i64, u16); 60], | ||
} | ||
|
||
impl RequestCounter { | ||
/// Creates a new request counter backed by `clock`. | ||
pub(crate) fn new(clock: Arc<dyn Clock + Send + Sync>) -> Self { | ||
Self { clock, counts: [(0, 0); 60] } | ||
} | ||
|
||
/// Adds a request to the counter at the current time. | ||
pub(crate) fn account(&mut self) { | ||
let now = self.clock.now_utc(); | ||
let i = usize::from(now.second()) % 60; | ||
let (ts, count) = self.counts[i]; | ||
if ts == now.unix_timestamp() { | ||
self.counts[i] = (ts, count + 1); | ||
} else { | ||
self.counts[i] = (now.unix_timestamp(), 1); | ||
} | ||
} | ||
|
||
/// Counts the number of requests during the last minute. | ||
pub(crate) fn last_minute(&self) -> usize { | ||
let now = self.clock.now_utc(); | ||
let since = (now - Duration::from_secs(60)).unix_timestamp(); | ||
|
||
let mut total = 0; | ||
for (ts, count) in self.counts { | ||
if ts > since { | ||
total += usize::from(count); | ||
} | ||
} | ||
total | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use iii_iv_core::clocks::testutils::SettableClock; | ||
use std::time::Duration; | ||
use time::macros::datetime; | ||
|
||
#[test] | ||
fn test_continuous() { | ||
let clock = Arc::from(SettableClock::new(datetime!(2023-09-26 18:20:15 UTC))); | ||
let mut counter = RequestCounter::new(clock.clone()); | ||
|
||
assert_eq!(0, counter.last_minute()); | ||
for i in 0..60 { | ||
clock.advance(Duration::from_secs(1)); | ||
counter.account(); | ||
counter.account(); | ||
assert_eq!((i + 1) * 2, counter.last_minute()); | ||
} | ||
assert_eq!(120, counter.last_minute()); | ||
for i in 0..60 { | ||
clock.advance(Duration::from_secs(1)); | ||
counter.account(); | ||
assert_eq!(120 - (i + 1), counter.last_minute()); | ||
} | ||
assert_eq!(60, counter.last_minute()); | ||
for i in 0..60 { | ||
clock.advance(Duration::from_secs(1)); | ||
assert_eq!(60 - (i + 1), counter.last_minute()); | ||
} | ||
assert_eq!(0, counter.last_minute()); | ||
} | ||
|
||
#[test] | ||
fn test_gaps() { | ||
let clock = Arc::from(SettableClock::new(datetime!(2023-09-26 17:20:56 UTC))); | ||
let mut counter = RequestCounter::new(clock.clone()); | ||
|
||
assert_eq!(0, counter.last_minute()); | ||
for _ in 0..1000 { | ||
counter.account(); | ||
} | ||
assert_eq!(1000, counter.last_minute()); | ||
|
||
clock.advance(Duration::from_secs(30)); | ||
counter.account(); | ||
assert_eq!(1001, counter.last_minute()); | ||
|
||
clock.advance(Duration::from_secs(29)); | ||
counter.account(); | ||
assert_eq!(1002, counter.last_minute()); | ||
|
||
clock.advance(Duration::from_secs(1)); | ||
counter.account(); | ||
assert_eq!(3, counter.last_minute()); | ||
} | ||
} |
Oops, something went wrong.