mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 09:48:44 +00:00
Merge branch 'master' into feature/weights_endpoints
This commit is contained in:
commit
08d3c389b9
34 files changed
+1803
-220
No files matched your search
Generated
+435
-194
File diff suppressed because it is too large.
Load diff
@@ -86,6 +86,7 @@ members = [
|
||||
"crates/service/plugins/users",
|
||||
|
||||
# Vendored code
|
||||
"crates/vendor/limitation",
|
||||
"crates/vendor/newrelic-telemetry-sdk-rust",
|
||||
|
||||
# Vendored code
|
||||
|
||||
@@ -18,7 +18,7 @@ path = "src/lib.rs"
|
||||
# (none)
|
||||
|
||||
# External
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
|
||||
@@ -13,7 +13,7 @@ name = "datetimes"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
once_cell = "1.18.0"
|
||||
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ reusable_types = { path = "../../../_types/reusable_types" }
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.0"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
once_cell = "1.18.0"
|
||||
regex = "1.5.4"
|
||||
|
||||
@@ -23,7 +23,7 @@ elasticsearch = "8.5.0-alpha.1"
|
||||
log = "0.4.14"
|
||||
|
||||
# Serialization
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.108"
|
||||
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
|
||||
|
||||
@@ -24,7 +24,7 @@ redis_common = { path = "../redis_common" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
r2d2_redis = "0.14.0"
|
||||
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
|
||||
@@ -25,7 +25,7 @@ tokens = { path = "../../_types/tokens" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
log = "0.4.14"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ user_input_common = { path = "../user_input_common" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
once_cell = "1.18.0"
|
||||
regex = "1.5.4"
|
||||
|
||||
@@ -19,7 +19,7 @@ errors = { path = "../../lib/errors" }
|
||||
|
||||
# External
|
||||
bytes = "1.3"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
once_cell = "1.18.0"
|
||||
strum = { version = "0.25", features = ["derive"] } # Fancy enum iteration, etc.
|
||||
|
||||
@@ -26,7 +26,7 @@ tokens = { path = "../../../_types/tokens" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -35,7 +35,7 @@ subprocess_common = { path = "../../../lib/subprocess_common" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -46,7 +46,7 @@ tts_common = { path = "../../../lib/tts_common" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
|
||||
@@ -43,7 +43,7 @@ newrelic-telemetry = { path = "../../../vendor/newrelic-telemetry-sdk-rust" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
lfu = "0.2.5"
|
||||
|
||||
@@ -28,7 +28,7 @@ mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -32,7 +32,7 @@ newrelic-telemetry = { path = "../../../vendor/newrelic-telemetry-sdk-rust" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
|
||||
@@ -28,7 +28,7 @@ mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -26,7 +26,7 @@ mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -28,7 +28,7 @@ twitch_common = { path = "../../../lib/api_clients/twitch_common" }
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.0"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
@@ -24,7 +24,7 @@ url_config = { path = "../../../lib/url_config" }
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.0"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
log = "0.4.14"
|
||||
once_cell = "1.18.0"
|
||||
|
||||
@@ -34,7 +34,7 @@ user_input_common = { path = "../../../lib/user_input_common" }
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.0"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
lazy_static = "1.4.0" # Regex caching (TODO: Use OnceCell instead)
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -21,7 +21,7 @@ errors = { path = "../../../lib/errors" }
|
||||
reusable_types = { path = "../../../_types/reusable_types" }
|
||||
|
||||
# External
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
#once_cell = "1.9.0"
|
||||
|
||||
@@ -58,7 +58,7 @@ users_component = { path = "../../plugins/users" }
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.0"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
derive_more = "0.99.17"
|
||||
dotenv = "0.15.0"
|
||||
hex = "0.4.3"
|
||||
@@ -104,7 +104,7 @@ pin-project-lite = "0.2.9" # NB: For middleware/futures upgrade
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
# Redis Database
|
||||
limitation = "0.1.1" # Redis rate limiter
|
||||
limitation = { path = "../../../vendor/limitation" }
|
||||
r2d2_redis = "0.14.0"
|
||||
|
||||
# Elasticsearch
|
||||
|
||||
@@ -27,7 +27,7 @@ twitch_common = { path = "../../../lib/api_clients/twitch_common" }
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.0"
|
||||
chrono = { version = "=0.4.31", features = ["serde"] }
|
||||
chrono = { version = "=0.4.22", features = ["serde"] }
|
||||
derive_more = "0.99.17"
|
||||
dotenv = "0.15.0"
|
||||
hostname = "0.3.1"
|
||||
@@ -50,7 +50,7 @@ futures_old_for_limiter = { package = "futures", version = "0.1.29" } # Older ve
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
# Redis Database
|
||||
limitation = "0.1.1" # Redis rate limiter
|
||||
limitation = { path = "../../../vendor/limitation" }
|
||||
r2d2_redis = "0.14.0"
|
||||
redis_async = { package = "redis", version = "0.21.5", features = ["tokio-comp"] }
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.1.1 / 2019-10-20
|
||||
|
||||
### Improvements
|
||||
|
||||
- Add `readme` to `Cargo.toml` (@fnichol)
|
||||
|
||||
## 0.1.0 / 2019-10-20
|
||||
|
||||
- The initial release.
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at fnichol@nichol.ca. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "limitation"
|
||||
version = "0.1.1"
|
||||
authors = ["Fletcher Nichol <fnichol@nichol.ca>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/fnichol/limitation"
|
||||
documentation = "https://docs.rs/limitation"
|
||||
readme = "README.md"
|
||||
keywords = ["rate-limit", "rate-limiting", "limiter", "redis"]
|
||||
categories = ["asynchronous", "web-programming"]
|
||||
description = """
|
||||
A rate limiter using a fixed window counter for arbitrary keys, backed by
|
||||
Redis.
|
||||
"""
|
||||
|
||||
[badges]
|
||||
cirrus-ci = { repository = "fnichol/limitation" }
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.9"
|
||||
futures = "0.1.29"
|
||||
redis = "0.13.0"
|
||||
time = "0.1.42"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = "0.1.22"
|
||||
version-sync = "0.8.1"
|
||||
Vendored
+373
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
[tasks.member-readme]
|
||||
dependencies = [
|
||||
"generate-readme",
|
||||
"update-readme-toc",
|
||||
"format-readme",
|
||||
]
|
||||
Vendored
+298
@@ -0,0 +1,298 @@
|
||||
# limitation
|
||||
|
||||
| | |
|
||||
| --------------: | ------------------------------------------- |
|
||||
| CI | [![CI Status][badge-ci-overall]][ci] |
|
||||
| Latest Version | [![Latest version][badge-version]][crate] |
|
||||
| Documentation | [![Documentation][badge-docs]][docs] |
|
||||
| Crate Downloads | [![Crate downloads][badge-crate-dl]][crate] |
|
||||
| License | [![Crate license][badge-license]][github] |
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [About](#about)
|
||||
- [Usage](#usage)
|
||||
- [Quick Example](#quick-example)
|
||||
- [The Limiter Builder](#the-limiter-builder)
|
||||
- [Examples](#examples)
|
||||
- [Related Projects and References](#related-projects-and-references)
|
||||
- [Ideas and Future Work](#ideas-and-future-work)
|
||||
- [CI Status](#ci-status)
|
||||
- [Build (master branch)](#build-master-branch)
|
||||
- [Test (master branch)](#test-master-branch)
|
||||
- [Check (master branch)](#check-master-branch)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [Issues](#issues)
|
||||
- [Contributing](#contributing)
|
||||
- [Release History](#release-history)
|
||||
- [Authors](#authors)
|
||||
- [License](#license)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
A rate limiter using a fixed window counter for arbitrary keys, backed by Redis.
|
||||
|
||||
## About
|
||||
|
||||
This library provides a fixed window counter for arbitrary keys backed by a
|
||||
Redis instance. The period length and per-period maximum limit are configurable
|
||||
on setup. The communication with the backend is performing in a non-blocking,
|
||||
asynchronous fashion allowing the library to pair with other async frameworks
|
||||
such as Tokio, Actix, etc.
|
||||
|
||||
_Note_: Currently pre-_async/await_ Futures are used (that is Futures 0.1.x) but
|
||||
that may be upgraded in the near future as the Rust 1.39.0 release is near.
|
||||
|
||||
## Usage
|
||||
|
||||
Add `limitation` to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
limitation = "0.1.1"
|
||||
```
|
||||
|
||||
### Quick Example
|
||||
|
||||
The primary type is the [`Limiter`] which uses a builder to construct itself.
|
||||
Once built, use the [`count`] method with a key representing a user, a session,
|
||||
an interaction, etc. Note that `count` is a Future and therefore has to be
|
||||
driving to completion with a runtime. For example, to run one count on a key of
|
||||
`"10.0.0.5"`:
|
||||
|
||||
[`limiter`]: struct.Limiter.html
|
||||
[`count`]: struct.Limiter.html#method.count
|
||||
|
||||
```rust
|
||||
use limitation::Limiter;
|
||||
use futures::Future;
|
||||
|
||||
// Build a Limiter with a default rate with a local running Redis instance
|
||||
let limiter = Limiter::build("redis://127.0.0.1/").finish()?;
|
||||
// The key to count and track
|
||||
let key = "10.0.0.5";
|
||||
|
||||
// Start and run a Tokio runtime to drive the Future to completion
|
||||
tokio::run(
|
||||
limiter
|
||||
// Count returns a Status if the key is under the limit and an `Error::LimitExceeded`
|
||||
// containing a Status if the limit has been exceeded
|
||||
.count(key)
|
||||
// This example deals with both limit exceeded and any Redis connection issues. In this
|
||||
// case we'll print the error to the standard error stream to show the current limit
|
||||
// status
|
||||
.map_err(|err| eprintln!("err: {}", err))
|
||||
// In this case we are under the limit and can print out the limit status to the
|
||||
// standard output stream
|
||||
.and_then(|status| {
|
||||
println!("ok: {:?}", status);
|
||||
Ok(())
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
### The Limiter Builder
|
||||
|
||||
The builder for the `Limiter` has 2 settings which can be customized to the use
|
||||
case:
|
||||
|
||||
- [`limit`]: The high water mark for number of requests in the period. The
|
||||
default is `5000`.
|
||||
- [`period`]: A `Duration` for the period window. The default is 60 minutes.
|
||||
|
||||
```rust
|
||||
use limitation::Limiter;
|
||||
use std::time::Duration;
|
||||
|
||||
let limiter = Limiter::build("redis://127.0.0.1/")
|
||||
.limit(5)
|
||||
.period(Duration::from_secs(10))
|
||||
.finish()?;
|
||||
```
|
||||
|
||||
[`limit`]: struct.Builder.html#method.limit
|
||||
[`period`]: struct.Builder.html#method.period
|
||||
|
||||
## Examples
|
||||
|
||||
A simple example that uses this library can be found in [limitation-example].
|
||||
|
||||
[limitation-example]:
|
||||
https://github.com/fnichol/limitation/tree/master/limitation-example
|
||||
|
||||
## Related Projects and References
|
||||
|
||||
The primary inspiration for the implementation was a blog post called [An
|
||||
alternative approach to building a simple API Rate limiter using NodeJS and
|
||||
Redis][blog-post] by [Atul R](https://twitter.com/masteratul94).
|
||||
|
||||
Other research and references used for this library:
|
||||
|
||||
- <https://nordicapis.com/everything-you-need-to-know-about-api-rate-limiting/>
|
||||
- <https://hechao.li/2018/06/25/Rate-Limiter-Part1/>
|
||||
- <https://www.figma.com/blog/an-alternative-approach-to-rate-limiting/>
|
||||
- <https://engagor.github.io/blog/2017/05/02/sliding-window-rate-limiter-redis/>
|
||||
|
||||
[blog-post]: https://blog.atulr.com/rate-limiter/
|
||||
|
||||
## Ideas and Future Work
|
||||
|
||||
These are some ideas and potential future work for this project. If you're
|
||||
reading this then maybe you're curious or interesting in helping out? Great! Be
|
||||
sure to check out the [Contributing] section and dig in!
|
||||
|
||||
- Investigate and offer alternative rate-limiting algorithms, notably a Sliding
|
||||
Window solution.
|
||||
- Add async Redis connection pooling with the `bb8` and `bb8-redis` crates to
|
||||
reduce connection establishment delays.
|
||||
- Add a `status` method on `Limiter` which returns they key's `Status` without
|
||||
counting a request.
|
||||
- Add `RedisServer` support in an integration testing suite, similar to the
|
||||
infrastructure in the [redis] crate.
|
||||
|
||||
[contributing]:
|
||||
https://github.com/fnichol/limitation/tree/master/limitation#contributing
|
||||
[redis]: https://github.com/mitsuhiko/redis-rs/blob/master/tests/support/mod.rs
|
||||
|
||||
## CI Status
|
||||
|
||||
### Build (master branch)
|
||||
|
||||
| Operating System | Stable Rust | Nightly Rust | <abbr title="Minimum Supported Rust Version">MSRV</abbr> |
|
||||
| ---------------: | ----------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| FreeBSD | [![FreeBSD Stable Build Status][badge-stable_freebsd-build]][ci-master] | [![FreeBSD Nightly Build Status][badge-nightly_freebsd-build]][ci-master] | [![FreeBSD Oldest Build Status][badge-oldest_freebsd-build]][ci-master] |
|
||||
| Linux | [![Linux Stable Build Status][badge-stable_linux-build]][ci-master] | [![Linux Nightly Build Status][badge-nightly_linux-build]][ci-master] | [![Linux Oldest Build Status][badge-oldest_linux-build]][ci-master] |
|
||||
| macOS | [![macOS Stable Build Status][badge-stable_macos-build]][ci-master] | [![macOS Nightly Build Status][badge-nightly_macos-build]][ci-master] | [![macOS Oldest Build Status][badge-oldest_macos-build]][ci-master] |
|
||||
| Windows | [![Windows Stable Build Status][badge-stable_windows-build]][ci-master] | [![Windows Nightly Build Status][badge-nightly_windows-build]][ci-master] | [![Windows Oldest Build Status][badge-oldest_windows-build]][ci-master] |
|
||||
|
||||
### Test (master branch)
|
||||
|
||||
| Operating System | Stable Rust | Nightly Rust | <abbr title="Minimum Supported Rust Version">MSRV</abbr> |
|
||||
| ---------------: | --------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| FreeBSD | [![FreeBSD Stable Test Status][badge-stable_freebsd-test]][ci-master] | [![FreeBSD Nightly Test Status][badge-nightly_freebsd-test]][ci-master] | [![FreeBSD Oldest Test Status][badge-oldest_freebsd-test]][ci-master] |
|
||||
| Linux | [![Linux Stable Test Status][badge-stable_linux-test]][ci-master] | [![Linux Nightly Test Status][badge-nightly_linux-test]][ci-master] | [![Linux Oldest Test Status][badge-oldest_linux-test]][ci-master] |
|
||||
| macOS | [![macOS Stable Test Status][badge-stable_macos-test]][ci-master] | [![macOS Nightly Test Status][badge-nightly_macos-test]][ci-master] | [![macOS Oldest Test Status][badge-oldest_macos-test]][ci-master] |
|
||||
| Windows | [![Windows Stable Test Status][badge-stable_windows-test]][ci-master] | [![Windows Nightly Test Status][badge-nightly_windows-test]][ci-master] | [![Windows Oldest Test Status][badge-oldest_windows-test]][ci-master] |
|
||||
|
||||
### Check (master branch)
|
||||
|
||||
| | Status |
|
||||
| ------ | ------------------------------------------------- |
|
||||
| Lint | [![Lint Status][badge-check-lint]][ci-master] |
|
||||
| Format | [![Format Status][badge-check-format]][ci-master] |
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to the Contributor Covenant [code of
|
||||
conduct][code-of-conduct]. By participating, you are expected to uphold this
|
||||
code. Please report unacceptable behavior to fnichol@nichol.ca.
|
||||
|
||||
## Issues
|
||||
|
||||
If you have any problems with or questions about this project, please contact us
|
||||
through a [GitHub issue][issues].
|
||||
|
||||
## Contributing
|
||||
|
||||
You are invited to contribute to new features, fixes, or updates, large or
|
||||
small; we are always thrilled to receive pull requests, and do our best to
|
||||
process them as fast as we can.
|
||||
|
||||
Before you start to code, we recommend discussing your plans through a [GitHub
|
||||
issue][issues], especially for more ambitious contributions. This gives other
|
||||
contributors a chance to point you in the right direction, give you feedback on
|
||||
your design, and help you find out if someone else is working on the same thing.
|
||||
|
||||
## Release History
|
||||
|
||||
See the [changelog] for a full release history.
|
||||
|
||||
## Authors
|
||||
|
||||
Created and maintained by [Fletcher Nichol][fnichol] (<fnichol@nichol.ca>).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the Mozilla Public License Version 2.0 ([LICENSE.txt][license]).
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the MPL-2.0 license, shall be
|
||||
licensed as above, without any additional terms or conditions.
|
||||
|
||||
[badge-check-format]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=check&script=format
|
||||
[badge-check-lint]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=check&script=lint
|
||||
[badge-ci-overall]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square
|
||||
[badge-crate-dl]:
|
||||
https://img.shields.io/crates/d/limitation.svg?style=flat-square
|
||||
[badge-docs]: https://docs.rs/limitation/badge.svg?style=flat-square
|
||||
[badge-license]:
|
||||
https://img.shields.io/crates/l/limitation.svg?style=flat-square
|
||||
[badge-nightly_freebsd-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_freebsd&script=build
|
||||
[badge-nightly_freebsd-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_freebsd&script=test
|
||||
[badge-nightly_linux-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_linux&script=build
|
||||
[badge-nightly_linux-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_linux&script=test
|
||||
[badge-nightly_macos-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_macos&script=build
|
||||
[badge-nightly_macos-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_macos&script=test
|
||||
[badge-nightly_windows-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_windows&script=build
|
||||
[badge-nightly_windows-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_windows&script=test
|
||||
[badge-oldest_freebsd-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_freebsd&script=build
|
||||
[badge-oldest_freebsd-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_freebsd&script=test
|
||||
[badge-oldest_linux-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_linux&script=build
|
||||
[badge-oldest_linux-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_linux&script=test
|
||||
[badge-oldest_macos-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_macos&script=build
|
||||
[badge-oldest_macos-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_macos&script=test
|
||||
[badge-oldest_windows-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_windows&script=build
|
||||
[badge-oldest_windows-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_windows&script=test
|
||||
[badge-stable_freebsd-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_freebsd&script=build
|
||||
[badge-stable_freebsd-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_freebsd&script=test
|
||||
[badge-stable_linux-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_linux&script=build
|
||||
[badge-stable_linux-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_linux&script=test
|
||||
[badge-stable_macos-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_macos&script=build
|
||||
[badge-stable_macos-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_macos&script=test
|
||||
[badge-stable_windows-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_windows&script=build
|
||||
[badge-stable_windows-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_windows&script=test
|
||||
[badge-version]:
|
||||
https://img.shields.io/crates/v/limitation.svg?style=flat-square
|
||||
[changelog]:
|
||||
https://github.com/fnichol/limitation/blob/master/limitation/CHANGELOG.md
|
||||
[ci]: https://cirrus-ci.com/github/fnichol/limitation
|
||||
[ci-master]: https://cirrus-ci.com/github/fnichol/limitation/master
|
||||
[code-of-conduct]:
|
||||
https://github.com/fnichol/limitation/blob/master/limitation/CODE_OF_CONDUCT.md
|
||||
[commonmark]: https://commonmark.org/
|
||||
[crate]: https://crates.io/crates/limitation
|
||||
[docs]: https://docs.rs/limitation
|
||||
[fnichol]: https://github.com/fnichol
|
||||
[github]: https://github.com/fnichol/limitation
|
||||
[issues]: https://github.com/fnichol/limitation/issues
|
||||
[license]:
|
||||
https://github.com/fnichol/limitation/blob/master/limitation/LICENSE.txt
|
||||
Vendored
+156
@@ -0,0 +1,156 @@
|
||||
# {{crate}}
|
||||
|
||||
| | |
|
||||
| --------------: | ------------------------------------------- |
|
||||
| CI | [![CI Status][badge-ci-overall]][ci] |
|
||||
| Latest Version | [![Latest version][badge-version]][crate] |
|
||||
| Documentation | [![Documentation][badge-docs]][docs] |
|
||||
| Crate Downloads | [![Crate downloads][badge-crate-dl]][crate] |
|
||||
| License | [![Crate license][badge-license]][github] |
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
{{readme}}
|
||||
|
||||
## CI Status
|
||||
|
||||
### Build (master branch)
|
||||
|
||||
| Operating System | Stable Rust | Nightly Rust | <abbr title="Minimum Supported Rust Version">MSRV</abbr> |
|
||||
| ---------------: | ----------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| FreeBSD | [![FreeBSD Stable Build Status][badge-stable_freebsd-build]][ci-master] | [![FreeBSD Nightly Build Status][badge-nightly_freebsd-build]][ci-master] | [![FreeBSD Oldest Build Status][badge-oldest_freebsd-build]][ci-master] |
|
||||
| Linux | [![Linux Stable Build Status][badge-stable_linux-build]][ci-master] | [![Linux Nightly Build Status][badge-nightly_linux-build]][ci-master] | [![Linux Oldest Build Status][badge-oldest_linux-build]][ci-master] |
|
||||
| macOS | [![macOS Stable Build Status][badge-stable_macos-build]][ci-master] | [![macOS Nightly Build Status][badge-nightly_macos-build]][ci-master] | [![macOS Oldest Build Status][badge-oldest_macos-build]][ci-master] |
|
||||
| Windows | [![Windows Stable Build Status][badge-stable_windows-build]][ci-master] | [![Windows Nightly Build Status][badge-nightly_windows-build]][ci-master] | [![Windows Oldest Build Status][badge-oldest_windows-build]][ci-master] |
|
||||
|
||||
### Test (master branch)
|
||||
|
||||
| Operating System | Stable Rust | Nightly Rust | <abbr title="Minimum Supported Rust Version">MSRV</abbr> |
|
||||
| ---------------: | --------------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| FreeBSD | [![FreeBSD Stable Test Status][badge-stable_freebsd-test]][ci-master] | [![FreeBSD Nightly Test Status][badge-nightly_freebsd-test]][ci-master] | [![FreeBSD Oldest Test Status][badge-oldest_freebsd-test]][ci-master] |
|
||||
| Linux | [![Linux Stable Test Status][badge-stable_linux-test]][ci-master] | [![Linux Nightly Test Status][badge-nightly_linux-test]][ci-master] | [![Linux Oldest Test Status][badge-oldest_linux-test]][ci-master] |
|
||||
| macOS | [![macOS Stable Test Status][badge-stable_macos-test]][ci-master] | [![macOS Nightly Test Status][badge-nightly_macos-test]][ci-master] | [![macOS Oldest Test Status][badge-oldest_macos-test]][ci-master] |
|
||||
| Windows | [![Windows Stable Test Status][badge-stable_windows-test]][ci-master] | [![Windows Nightly Test Status][badge-nightly_windows-test]][ci-master] | [![Windows Oldest Test Status][badge-oldest_windows-test]][ci-master] |
|
||||
|
||||
### Check (master branch)
|
||||
|
||||
| | Status |
|
||||
| ------ | ------------------------------------------------- |
|
||||
| Lint | [![Lint Status][badge-check-lint]][ci-master] |
|
||||
| Format | [![Format Status][badge-check-format]][ci-master] |
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to the Contributor Covenant [code of
|
||||
conduct][code-of-conduct]. By participating, you are expected to uphold this
|
||||
code. Please report unacceptable behavior to fnichol@nichol.ca.
|
||||
|
||||
## Issues
|
||||
|
||||
If you have any problems with or questions about this project, please contact us
|
||||
through a [GitHub issue][issues].
|
||||
|
||||
## Contributing
|
||||
|
||||
You are invited to contribute to new features, fixes, or updates, large or
|
||||
small; we are always thrilled to receive pull requests, and do our best to
|
||||
process them as fast as we can.
|
||||
|
||||
Before you start to code, we recommend discussing your plans through a [GitHub
|
||||
issue][issues], especially for more ambitious contributions. This gives other
|
||||
contributors a chance to point you in the right direction, give you feedback on
|
||||
your design, and help you find out if someone else is working on the same thing.
|
||||
|
||||
## Release History
|
||||
|
||||
See the [changelog] for a full release history.
|
||||
|
||||
## Authors
|
||||
|
||||
Created and maintained by [Fletcher Nichol][fnichol] (<fnichol@nichol.ca>).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the Mozilla Public License Version 2.0 ([LICENSE.txt][license]).
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the MPL-2.0 license, shall be
|
||||
licensed as above, without any additional terms or conditions.
|
||||
|
||||
[badge-check-format]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=check&script=format
|
||||
[badge-check-lint]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=check&script=lint
|
||||
[badge-ci-overall]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square
|
||||
[badge-crate-dl]:
|
||||
https://img.shields.io/crates/d/limitation.svg?style=flat-square
|
||||
[badge-docs]: https://docs.rs/limitation/badge.svg?style=flat-square
|
||||
[badge-license]:
|
||||
https://img.shields.io/crates/l/limitation.svg?style=flat-square
|
||||
[badge-nightly_freebsd-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_freebsd&script=build
|
||||
[badge-nightly_freebsd-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_freebsd&script=test
|
||||
[badge-nightly_linux-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_linux&script=build
|
||||
[badge-nightly_linux-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_linux&script=test
|
||||
[badge-nightly_macos-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_macos&script=build
|
||||
[badge-nightly_macos-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_macos&script=test
|
||||
[badge-nightly_windows-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_windows&script=build
|
||||
[badge-nightly_windows-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_nightly_windows&script=test
|
||||
[badge-oldest_freebsd-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_freebsd&script=build
|
||||
[badge-oldest_freebsd-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_freebsd&script=test
|
||||
[badge-oldest_linux-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_linux&script=build
|
||||
[badge-oldest_linux-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_linux&script=test
|
||||
[badge-oldest_macos-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_macos&script=build
|
||||
[badge-oldest_macos-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_macos&script=test
|
||||
[badge-oldest_windows-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_windows&script=build
|
||||
[badge-oldest_windows-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_1.35.0_windows&script=test
|
||||
[badge-stable_freebsd-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_freebsd&script=build
|
||||
[badge-stable_freebsd-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_freebsd&script=test
|
||||
[badge-stable_linux-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_linux&script=build
|
||||
[badge-stable_linux-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_linux&script=test
|
||||
[badge-stable_macos-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_macos&script=build
|
||||
[badge-stable_macos-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_macos&script=test
|
||||
[badge-stable_windows-build]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_windows&script=build
|
||||
[badge-stable_windows-test]:
|
||||
https://img.shields.io/cirrus/github/fnichol/limitation.svg?style=flat-square&task=test_stable_windows&script=test
|
||||
[badge-version]:
|
||||
https://img.shields.io/crates/v/limitation.svg?style=flat-square
|
||||
[changelog]:
|
||||
https://github.com/fnichol/limitation/blob/master/limitation/CHANGELOG.md
|
||||
[ci]: https://cirrus-ci.com/github/fnichol/limitation
|
||||
[ci-master]: https://cirrus-ci.com/github/fnichol/limitation/master
|
||||
[code-of-conduct]:
|
||||
https://github.com/fnichol/limitation/blob/master/limitation/CODE_OF_CONDUCT.md
|
||||
[commonmark]: https://commonmark.org/
|
||||
[crate]: https://crates.io/crates/limitation
|
||||
[docs]: https://docs.rs/limitation
|
||||
[fnichol]: https://github.com/fnichol
|
||||
[github]: https://github.com/fnichol/limitation
|
||||
[issues]: https://github.com/fnichol/limitation/issues
|
||||
[license]:
|
||||
https://github.com/fnichol/limitation/blob/master/limitation/LICENSE.txt
|
||||
Vendored
+377
@@ -0,0 +1,377 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! A rate limiter using a fixed window counter for arbitrary keys, backed by Redis.
|
||||
//!
|
||||
//! # About
|
||||
//!
|
||||
//! This library provides a fixed window counter for arbitrary keys backed by a Redis instance. The
|
||||
//! period length and per-period maximum limit are configurable on setup. The communication with
|
||||
//! the backend is performing in a non-blocking, asynchronous fashion allowing the library to pair
|
||||
//! with other async frameworks such as Tokio, Actix, etc.
|
||||
//!
|
||||
//! *Note*: Currently pre-*async/await* Futures are used (that is Futures 0.1.x) but that may be
|
||||
//! upgraded in the near future as the Rust 1.39.0 release is near.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Add `limitation` to your `Cargo.toml`:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! limitation = "0.1.1"
|
||||
//! ```
|
||||
//!
|
||||
//! ## Quick Example
|
||||
//!
|
||||
//! The primary type is the [`Limiter`] which uses a builder to construct itself. Once built, use
|
||||
//! the [`count`] method with a key representing a user, a session, an interaction, etc. Note that
|
||||
//! `count` is a Future and therefore has to be driving to completion with a runtime. For example,
|
||||
//! to run one count on a key of `"10.0.0.5"`:
|
||||
//!
|
||||
//! [`Limiter`]: struct.Limiter.html
|
||||
//! [`count`]: struct.Limiter.html#method.count
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use limitation::Limiter;
|
||||
//! use futures::Future;
|
||||
//!
|
||||
//! // Build a Limiter with a default rate with a local running Redis instance
|
||||
//! let limiter = Limiter::build("redis://127.0.0.1/").finish()?;
|
||||
//! // The key to count and track
|
||||
//! let key = "10.0.0.5";
|
||||
//!
|
||||
//! // Start and run a Tokio runtime to drive the Future to completion
|
||||
//! tokio::run(
|
||||
//! limiter
|
||||
//! // Count returns a Status if the key is under the limit and an `Error::LimitExceeded`
|
||||
//! // containing a Status if the limit has been exceeded
|
||||
//! .count(key)
|
||||
//! // This example deals with both limit exceeded and any Redis connection issues. In this
|
||||
//! // case we'll print the error to the standard error stream to show the current limit
|
||||
//! // status
|
||||
//! .map_err(|err| eprintln!("err: {}", err))
|
||||
//! // In this case we are under the limit and can print out the limit status to the
|
||||
//! // standard output stream
|
||||
//! .and_then(|status| {
|
||||
//! println!("ok: {:?}", status);
|
||||
//! Ok(())
|
||||
//! }),
|
||||
//! );
|
||||
//! # Ok::<(), limitation::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! ## The Limiter Builder
|
||||
//!
|
||||
//! The builder for the `Limiter` has 2 settings which can be customized to the use case:
|
||||
//!
|
||||
//! - [`limit`]: The high water mark for number of requests in the period. The default is `5000`.
|
||||
//! - [`period`]: A `Duration` for the period window. The default is 60 minutes.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use limitation::Limiter;
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! let limiter = Limiter::build("redis://127.0.0.1/")
|
||||
//! .limit(5)
|
||||
//! .period(Duration::from_secs(10))
|
||||
//! .finish()?;
|
||||
//! # Ok::<(), limitation::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! [`limit`]: struct.Builder.html#method.limit
|
||||
//! [`period`]: struct.Builder.html#method.period
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! A simple example that uses this library can be found in [limitation-example].
|
||||
//!
|
||||
//! [limitation-example]: https://github.com/fnichol/limitation/tree/master/limitation-example
|
||||
//!
|
||||
//! # Related Projects and References
|
||||
//!
|
||||
//! The primary inspiration for the implementation was a blog post called [An alternative approach
|
||||
//! to building a simple API Rate limiter using NodeJS and Redis][blog-post] by [Atul
|
||||
//! R](https://twitter.com/masteratul94).
|
||||
//!
|
||||
//! Other research and references used for this library:
|
||||
//!
|
||||
//! - <https://nordicapis.com/everything-you-need-to-know-about-api-rate-limiting/>
|
||||
//! - <https://hechao.li/2018/06/25/Rate-Limiter-Part1/>
|
||||
//! - <https://www.figma.com/blog/an-alternative-approach-to-rate-limiting/>
|
||||
//! - <https://engagor.github.io/blog/2017/05/02/sliding-window-rate-limiter-redis/>
|
||||
//!
|
||||
//! [blog-post]: https://blog.atulr.com/rate-limiter/
|
||||
//!
|
||||
//! # Ideas and Future Work
|
||||
//!
|
||||
//! These are some ideas and potential future work for this project. If you're reading this then
|
||||
//! maybe you're curious or interesting in helping out? Great! Be sure to check out the
|
||||
//! [Contributing] section and dig in!
|
||||
//!
|
||||
//! - Investigate and offer alternative rate-limiting algorithms, notably a Sliding Window
|
||||
//! solution.
|
||||
//! - Add async Redis connection pooling with the `bb8` and `bb8-redis` crates to reduce
|
||||
//! connection establishment delays.
|
||||
//! - Add a `status` method on `Limiter` which returns they key's `Status` without counting a
|
||||
//! request.
|
||||
//! - Add `RedisServer` support in an integration testing suite, similar to the infrastructure in
|
||||
//! the [redis] crate.
|
||||
//!
|
||||
//! [Contributing]: https://github.com/fnichol/limitation/tree/master/limitation#contributing
|
||||
//! [redis]: https://github.com/mitsuhiko/redis-rs/blob/master/tests/support/mod.rs
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/limitation/0.1.1")]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use chrono::SubsecRound;
|
||||
use futures::Future;
|
||||
use redis::Client;
|
||||
use std::convert::TryInto;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::ops::Add;
|
||||
use std::time::Duration;
|
||||
|
||||
/// The default limit of requests in a period
|
||||
const DEFAULT_LIMIT: usize = 5000;
|
||||
/// The default length of the period in seconds
|
||||
const DEFAULT_PERIOD_SECS: u64 = 60 * 60;
|
||||
|
||||
/// A rate limiter using a fixed window counter, backed by Redis.
|
||||
///
|
||||
/// The per-period limit and the period duration are customizable when building an instance.
|
||||
///
|
||||
/// The [`count`] method is the primary unit of interaction which requires a key representing a
|
||||
/// user, a session, an interaction, etc. This method returns a Future which needs a runtime to
|
||||
/// drive the task to completion asynchronously.
|
||||
///
|
||||
/// [`count`]: #method.count
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Limiter {
|
||||
/// The Redis client
|
||||
client: Client,
|
||||
/// The per-period limit
|
||||
limit: usize,
|
||||
/// The period duration
|
||||
period: Duration,
|
||||
}
|
||||
|
||||
impl Limiter {
|
||||
/// Returns a builder for a `Limiter`.
|
||||
///
|
||||
/// The [`finish`] method will build the final `Limiter` and perform a **synchronous**
|
||||
/// connection test to the Redis backend.
|
||||
///
|
||||
/// [`finish`]: struct.Builder.html#method.finish
|
||||
pub fn build(redis_url: &str) -> Builder {
|
||||
Builder {
|
||||
redis_url,
|
||||
limit: DEFAULT_LIMIT,
|
||||
period: Duration::from_secs(DEFAULT_PERIOD_SECS),
|
||||
}
|
||||
}
|
||||
|
||||
/// Counts a request on a key over a period and returns a [`Status`].
|
||||
///
|
||||
/// The `Status` type gives the caller a current state snapshot for the given key. If the limit
|
||||
/// is exceeded a `Error::LimitExceeded` will be returned which also contains a `Status`.
|
||||
/// Critically, the `Status` contains a time when the next period begins and the limit will
|
||||
/// reset. The time is a "unix timestamp" in UTC time.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an `Err` if:
|
||||
///
|
||||
/// - The limit has been exceeded in the current period
|
||||
/// - A client error has occurred
|
||||
/// - A time computation failed
|
||||
///
|
||||
/// [`Status`]: struct.Status.html
|
||||
pub fn count<K: Into<String>>(&self, key: K) -> impl Future<Item = Status, Error = Error> {
|
||||
let limit = self.limit;
|
||||
|
||||
self.track(key).and_then(move |(count, reset_epoch_utc)| {
|
||||
let status = build_status(count, limit, reset_epoch_utc);
|
||||
|
||||
if count > limit {
|
||||
Err(Error::LimitExceeded(status))
|
||||
} else {
|
||||
Ok(status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Tracks the given key in a period and returns the count and TTL for the key in seconds.
|
||||
fn track<K: Into<String>>(&self, key: K) -> impl Future<Item = (usize, usize), Error = Error> {
|
||||
let key = key.into();
|
||||
let exipres = self.period.as_secs();
|
||||
|
||||
self.client
|
||||
.get_async_connection()
|
||||
.from_err()
|
||||
.and_then(move |con| {
|
||||
// The seed of this approach is outlined Atul R in a blog post about rate limiting
|
||||
// using NodeJS and Redis. For more details, see
|
||||
// https://blog.atulr.com/rate-limiter/
|
||||
let mut pipe = redis::pipe();
|
||||
pipe.atomic()
|
||||
.cmd("SET")
|
||||
.arg(&key)
|
||||
.arg(0)
|
||||
.arg("EX")
|
||||
.arg(exipres)
|
||||
.arg("NX")
|
||||
.ignore()
|
||||
.cmd("INCR")
|
||||
.arg(&key)
|
||||
.cmd("TTL")
|
||||
.arg(&key);
|
||||
|
||||
pipe.query_async(con)
|
||||
.from_err()
|
||||
.and_then(|(_, (count, ttl)): (_, (usize, u64))| {
|
||||
Ok((count, epoch_utc_plus(Duration::from_secs(ttl))?))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A report for a given key containing the limit status.
|
||||
///
|
||||
/// The status contains the following information:
|
||||
///
|
||||
/// - [`limit`]: the maximum number of requests allowed in the current period
|
||||
/// - [`remaining`]: how many requests are left in the current period
|
||||
/// - [`reset_epoch_utc`]: a UNIX timestamp in UTC approximately when the next period will begin
|
||||
///
|
||||
/// [`limit`]: #method.limit
|
||||
/// [`remaining`]: #method.remaining
|
||||
/// [`reset_epoch_utc`]: #method.reset_epoch_utc
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Status {
|
||||
limit: usize,
|
||||
remaining: usize,
|
||||
reset_epoch_utc: usize,
|
||||
}
|
||||
|
||||
impl Status {
|
||||
/// Returns the maximum number of requests permitted in the current period.
|
||||
pub fn limit(&self) -> usize {
|
||||
self.limit
|
||||
}
|
||||
|
||||
/// Returns the number of requests remaining in the current period.
|
||||
pub fn remaining(&self) -> usize {
|
||||
self.remaining
|
||||
}
|
||||
|
||||
/// Returns a UNIX timestamp in UTC approximately when the next period will begin.
|
||||
pub fn reset_epoch_utc(&self) -> usize {
|
||||
self.reset_epoch_utc
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for a [`Limiter`].
|
||||
///
|
||||
/// [`Limiter`]: struct.Limiter.html
|
||||
pub struct Builder<'a> {
|
||||
redis_url: &'a str,
|
||||
limit: usize,
|
||||
period: Duration,
|
||||
}
|
||||
|
||||
impl Builder<'_> {
|
||||
/// Sets a new maximum limit for the Limiter.
|
||||
pub fn limit(&mut self, limit: usize) -> &mut Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a new period duration for the Limiter.
|
||||
pub fn period(&mut self, period: Duration) -> &mut Self {
|
||||
self.period = period;
|
||||
self
|
||||
}
|
||||
|
||||
/// Finializes and returns a `Limiter`.
|
||||
///
|
||||
/// Note that this method will connect to the Redis server to test its connection which is a
|
||||
/// **synchronous** operation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an `Err` if the Redis client fails to be created or fails to connect.
|
||||
pub fn finish(&self) -> Result<Limiter, Error> {
|
||||
Ok(Limiter {
|
||||
client: Client::open(self.redis_url)?,
|
||||
limit: self.limit,
|
||||
period: self.period,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for this crate.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The Redis client failed to connect or run a query.
|
||||
Client(redis::RedisError),
|
||||
/// The limit is exceeded for a key.
|
||||
LimitExceeded(Status),
|
||||
/// A time conversion failed.
|
||||
Time(time::OutOfRangeError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::Client(ref err) => write!(f, "client error ({})", err),
|
||||
Error::LimitExceeded(ref status) => write!(f, "rate limit exceeded ({:?})", status),
|
||||
Error::Time(ref err) => write!(f, "time conversion error ({})", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match self {
|
||||
Error::Client(ref err) => err.source(),
|
||||
Error::LimitExceeded(_) => None,
|
||||
Error::Time(ref err) => err.source(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<redis::RedisError> for Error {
|
||||
fn from(err: redis::RedisError) -> Self {
|
||||
Error::Client(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::OutOfRangeError> for Error {
|
||||
fn from(err: time::OutOfRangeError) -> Self {
|
||||
Error::Time(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a `Status`.
|
||||
fn build_status(count: usize, limit: usize, reset_epoch_utc: usize) -> Status {
|
||||
let remaining = if count >= limit { 0 } else { limit - count };
|
||||
|
||||
Status {
|
||||
limit,
|
||||
remaining,
|
||||
reset_epoch_utc,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a timestamp for "now plus a duration".
|
||||
fn epoch_utc_plus(duration: Duration) -> Result<usize, time::OutOfRangeError> {
|
||||
Ok(chrono::Utc::now()
|
||||
.add(chrono::Duration::from_std(duration)?)
|
||||
.round_subsecs(0)
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.unwrap_or(0))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#[test]
|
||||
fn test_readme_deps() {
|
||||
version_sync::assert_markdown_deps_updated!("README.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_html_root_url() {
|
||||
version_sync::assert_html_root_url_updated!("src/lib.rs");
|
||||
}
|
||||
@@ -22,7 +22,7 @@ bitflags = { version = "2.4.1", default-features = false, features = ["serde", "
|
||||
block-buffer = { version = "0.9.0", default-features = false, features = ["block-padding"] }
|
||||
bytemuck = { version = "1.14.0", default-features = false, features = ["extern_crate_alloc"] }
|
||||
byteorder = { version = "1.5.0" }
|
||||
chrono = { version = "0.4.31", features = ["alloc", "serde"] }
|
||||
chrono = { version = "0.4.22", features = ["alloc", "serde"] }
|
||||
combine = { version = "4.6.6", default-features = false, features = ["std", "tokio"] }
|
||||
crossbeam-utils = { version = "0.8.16" }
|
||||
deranged = { version = "0.3.9", default-features = false, features = ["powerfmt", "serde", "std"] }
|
||||
@@ -88,7 +88,7 @@ base64ct = { version = "1.6.0", default-features = false, features = ["alloc"] }
|
||||
bitflags = { version = "2.4.1", default-features = false, features = ["serde", "std"] }
|
||||
byteorder = { version = "1.5.0" }
|
||||
cc = { version = "1.0.83", default-features = false, features = ["parallel"] }
|
||||
chrono = { version = "0.4.31", features = ["alloc", "serde"] }
|
||||
chrono = { version = "0.4.22", features = ["alloc", "serde"] }
|
||||
crossbeam-utils = { version = "0.8.16" }
|
||||
derive_more = { version = "0.99.17" }
|
||||
digest = { version = "0.10.7", features = ["mac", "oid", "std"] }
|
||||
|
||||
Reference in new issue
Block a user