Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
79ff08e
feat: added pedantic lint
Mettwasser Feb 21, 2025
afd1bf4
style: replaced model builder macro with a new proc macro
Mettwasser Feb 22, 2025
ac11ce0
docs: fix license
Mettwasser Feb 22, 2025
ad274bb
style: consistency
Mettwasser Feb 23, 2025
0970770
chore: merge branch 'rstest' into v7
Mettwasser Feb 23, 2025
3c47435
fix: revamped tests, fixed tests and docs
Mettwasser Feb 23, 2025
3efee05
docs: bump MSRV to 1.85
Mettwasser Feb 23, 2025
b4ee699
feat: item wrapper for easier queries
Mettwasser Feb 24, 2025
88bc885
chore: remove obsolete test
Mettwasser Feb 24, 2025
3648125
fix: module tree
Mettwasser Mar 22, 2025
3ff7272
feat: part of market V2 + cache
Mettwasser Mar 23, 2025
d61872d
ci: fix CI
Mettwasser Mar 23, 2025
0610601
fix: cache issue and add item set endpoint
Mettwasser Mar 27, 2025
f8c9a74
feat: refactor, updated cache to by-url instead of type id
Mettwasser Mar 29, 2025
96ebf99
feat: cached slug validation
Mettwasser Mar 29, 2025
9fed3ff
feat: utilities for slugs
Mettwasser Mar 29, 2025
b79cc43
refactor: small refactor
Mettwasser Mar 30, 2025
784533d
fix: missing check + consistency
Mettwasser Apr 1, 2025
a3a2767
docs: improved/fixed documentation for most structs
Mettwasser Apr 1, 2025
f08003d
feat: deep archimedea model
Mettwasser Apr 2, 2025
e4fcfed
fix: deep archimedea naming
Mettwasser Apr 5, 2025
09b5151
docs: included coverage not for the wfm api
Mettwasser Apr 5, 2025
bbc9733
feat: riven/weapons endpoint and refactor
Mettwasser Apr 5, 2025
fd3f423
feat: every "data endpoint" model
Mettwasser Apr 5, 2025
992b90c
feat: orders
Mettwasser Apr 16, 2025
82ae0c6
fix: setitems fields not public
Mettwasser Apr 17, 2025
043eafb
chore: fix nushell script
Mettwasser Apr 17, 2025
3d29764
feat: the rest of all public market endpoints
Mettwasser Apr 17, 2025
80748c6
feat: add tags to items
Mettwasser Jun 17, 2025
58ec09c
fix: coderabbit catches
Mettwasser Jun 17, 2025
3b227ca
perf: remove unnecessary
Mettwasser Jun 17, 2025
9a4d5c4
feat: change function name to be more explicit
Mettwasser Jul 5, 2025
cfd4268
chore: code cleanup
Mettwasser Jul 5, 2025
a8b4956
feat: unstable profile impl, wont be included in release
Mettwasser Jul 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2

- name: Test Worldstate with all features
run: cargo test --all-features

- name: Cargo build
run: cargo build --release
run: cargo build

- name: Cargo build with all features
run: cargo build --all-features

- name: Semantic Release
id: release
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
.vscode/settings.json
.vscode/launch.json
.idea/
*.iml
97 changes: 36 additions & 61 deletions .vscode/test.code-snippets
Original file line number Diff line number Diff line change
@@ -1,78 +1,53 @@
{
// Place your warframe workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
"Test a model (RTObj)": {
"Test a model": {
"scope": "rust",
"prefix": "model_test",
"body": [
"#[cfg(test)]",
"mod test {",
" use super::${1:model};",
" use crate::worldstate::{client::Client, error::ApiError};",
"",
" #[cfg(not(feature = \"multilangual\"))]",
" #[tokio::test]",
" async fn test_${1/(.*)/${1:/downcase}/}() -> Result<(), ApiError> {",
" let client = Client::new();",
"mod test_${1/(.*)/${1:/downcase}/} {",
" use rstest::rstest;",
" use serde_json::from_str;",
"",
" match client.fetch::<${1:model}>().await {",
" Ok(_${1/(.*)/${1:/downcase}/}) => Ok(()),",
" Err(why) => Err(why),",
" }",
" use super::${1:model};",
" use crate::{",
" worldstate::{",
" fixtures::${1/(.*)/${1:/downcase}/}::{",
" ${1/(.*)/${1:/downcase}/},",
" ${1/(.*)/${1:/downcase}/}_en,",
" },",
" models::Queryable,",
" },",
" };",
"",
" type R = <${1:model} as Queryable>::Return;",
"",
" #[rstest]",
" fn test(${1/(.*)/${1:/downcase}/}_en: &str) {",
" from_str::<R>(${1/(.*)/${1:/downcase}/}_en).unwrap();",
" }",
"",
" #[cfg(feature = \"multilangual\")]",
" #[tokio::test]",
" async fn test_${1/(.*)/${1:/downcase}/}_ml() -> Result<(), ApiError> {",
" use crate::worldstate::prelude::Language;",
"",
" let client = Client::new();",
"",
" match client.fetch_using_lang::<${1:model}>(Language::ZH).await {",
" Ok(_${1/(.*)/${1:/downcase}/}) => Ok(()),",
" Err(why) => Err(why),",
" }",
" #[rstest]",
" fn test_ml(${1/(.*)/${1:/downcase}/}: &str) {",
" from_str::<R>(${1/(.*)/${1:/downcase}/}).unwrap();",
" }",
"}",
]
},
"Test a model (RTArray)": {
"Fixture": {
"scope": "rust",
"prefix": "model_test_array",
"prefix": "fixture",
"body": [
"#[cfg(test)]",
"mod test {",
" use super::${1:model};",
" use crate::worldstate::{client::Client, error::ApiError};",
"",
" #[cfg(not(feature = \"multilangual\"))]",
" #[tokio::test]",
" async fn test_${1/(.*)/${1:/downcase}/}() -> Result<(), ApiError> {",
" let client = Client::new();",
"",
" match client.fetch::<${1:model}>().await {",
" Ok(_${1/(.*)/${1:/downcase}/}s) => Ok(()),",
" Err(why) => Err(why),",
" }",
" }",
"",
" #[cfg(feature = \"multilangual\")]",
" #[tokio::test]",
" async fn test_${1/(.*)/${1:/downcase}/}_ml() -> Result<(), ApiError> {",
" use crate::worldstate::prelude::Language;",
"",
" let client = Client::new();",
"",
" match client.fetch_using_lang::<${1:model}>(Language::ZH).await {",
" Ok(_${1/(.*)/${1:/downcase}/}s) => Ok(()),",
" Err(why) => Err(why),",
" }",
" }",
"use crate::fixture;",
"",
"fixture! {",
" $1,",
"r#\"",
"$2",
"\"#",
"---",
"r#\"",
"$3",
"\"#",
"}",
]
}
Expand Down
95 changes: 0 additions & 95 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,98 +19,3 @@ Since the `market` module is rather small and easy to understand, we'll talk abo

## Worldstate module
All the models are defined via a function-like macro in the `worldstate/models` folder.

### The `model_builder!` macro
For example, let's look at the definition for `Cetus`:
```rs
model_builder! {
:"The Information about cetus"
Cetus: "/cetusCycle",
rt = obj,
timed = true;

:"The id of the cycle"
pub id: String,

:"The state of Cetus (day/night)"
pub state: CetusState,
}
```
Doc strings are made using the `:"doc here"` syntax. Followed by the `TypeName: "/endpoint_url"`. Said endpoints get concatenated via
```rs
concat!("https://api.warframestat.us/pc", $endpoint, "/?language=en")
```
at compile time. This prevents unnecessary allocations. Of course, this doesn't work when you want to query in another language.

When a type has this optional `: "/endpoint"`, it will implement the `Endpoint` trait like so:

```rs
impl Endpoint for $struct_name {
fn endpoint_en() -> &'static str {
concat!("https://api.warframestat.us/pc", $endpoint, "/?language=en")
}

#[cfg(feature = "multilangual")]
fn endpoint(language: Language) -> String {
format!(
"https://api.warframestat.us/pc{}/?language={}",
$endpoint,
String::from(language)
)
}
}
```

This is followed by an `rt = obj/arr`, which tells the model in which format it is returned in.
For example, there are no more than 1 `Cetus` at a time, so the API responds with a single `Cetus` object, hence `rt = obj`. `Fissure`s on the other hand have multiple active at a time, so the API responds with an array of those fissures, hence on fissures it's `rt = arr`.

Next is `timed = true`. This is some trickery, because models who have this set to true will get 2 fields: `activation` and `expiry`, and will additionally implement the `TimedEvent` trait.

### Putting it all together
To understand this, lets look at the `Queryable` trait first:
```rs
pub trait Queryable: Endpoint {
type Return: DeserializeOwned;
fn query(
request_executor: &reqwest::Client,
) -> impl std::future::Future<Output = Result<Self::Return, ApiError>> + Send {
async {
Ok(request_executor
.get(Self::endpoint_en())
.send()
.await?
.json::<Self::Return>()
.await?)
}
}

#[cfg(feature = "multilangual")]
fn query_with_language(
...
}
```

if a model has the endpoint signature (`: "/endpoint"`), the `Queryable` trait will be implemented by the macro.
Based on the `rt`, the `type Return` will either be `Self`, or `Vec<Self>`.

Now, all the `Client`'s `fetch` does:
```rs
impl Client {
pub async fn fetch<T>(&self) -> Result<T::Return, ApiError>
where
T: Queryable,
{
<T as Queryable>::query(&self.session).await
}
}
```

This means, depending on the type queried, you get a `Vec<Model>`, or a single `Model`.

E.g.
```rs
let fissures: Vec<Fissure> = client.fetch<Fissure>().await?;
let cetus: Cetus = client.fetch<Cetus>().await?;
```

If you have any questions, feel free to ask on the discord, or open an issue.
44 changes: 26 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
[package]
name = "warframe"
version = "6.2.0"
edition = "2021"
edition = "2024"
description = "An async crate to wrap Warframe's Worldstate API."
readme = "./README.md"
documentation = "https://docs.rs/warframe"
homepage = "https://docs.rs/warframe"
repository = "https://github.com/Mettwasser/warframe.rs"
license = "MIT"
rust-version = "1.85"


[features]
default = ["worldstate"]
default = ["market_ratelimit", "market_cache"]

full = ["worldstate_full", "market_full"]
worldstate = []
multilangual = ["worldstate"]
worldstate_listeners = ["worldstate"]
worldstate_full = ["worldstate", "multilangual", "worldstate_listeners"]
market = []
market_cache = ["market", "dep:moka"]
market_full = ["market", "market_cache"]
full = ["market_ratelimit", "market_cache"]

market_ratelimit = ["dep:governor"]
market_cache = ["dep:moka"]

[dependencies]
tokio = { version = "1.39.3", features = ["full"] }
Expand All @@ -30,14 +27,25 @@ serde = { version = "1.0.209", features = ["derive"] }
serde_json = { version = "1.0.127" }
serde_repr = "0.1.19"
futures = "0.3.30"
log = "0.4.22"
env_logger = "0.11.5"
thiserror = "1.0.63"
thiserror = "2.0.11"
moka = { version = "0.12.8", optional = true, features = ["future"] }
urlencoding = "2.1.3"
derive_more = { version = "1.0.0", features = ["full"] }
serde_with = "3.11.0"
derive_more = { version = "2.0.1", features = ["full"] }
serde_with = { version = "3.11.0" }
warframe-macros = { path = "warframe-macros" }
paste = "1.0.15"
tracing = "0.1.41"
governor = { version = "0.10.0", optional = true }
derive_builder = "0.20.2"
heck = "0.5.0"

[dev-dependencies]
rstest = "0.25.0"
tracing-subscriber = "0.3.19"

[lints.clippy]
pedantic = "warn"

# TODO: use this lint in V7
# [lints.clippy]
# pedantic = "warn"
[workspace]
resolver = "3"
members = ["warframe-macros", "."]
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 [fullname]
Copyright (c) 2023 Mettwasser

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
# warframe.rs

An async crate to wrap the [Worldstate API](https://docs.warframestat.us).
An async crate to wrap the [Worldstate API](https://docs.warframestat.us) and the [warframe.market API](https://42bytes.notion.site/WFM-Api-v2-Documentation-5d987e4aa2f74b55a80db1a09932459d).

Use this crate if you want to make a Warframe-related rust project that is async.

## Getting started
To install, simply run `cargo add warframe`.

Note that the MSRV of this project is `1.85`.

### Example
```rust,no_run
use warframe::worldstate::prelude::*;
use warframe::worldstate::{Client, Error, queryable::Cetus, Opposite, TimedEvent};

#[tokio::main]
async fn main() -> Result<(), ApiError> {
async fn main() -> Result<(), Error> {
let client = Client::new();

match client.fetch::<Cetus>().await {
Ok(cetus) => {
println!(
"It is currently {} on cetus. It will be {} in {}",
cetus.state,
cetus.state.opposite(),
cetus.eta()
);
Ok(())
}
Err(why) => Err(why),
}
let cetus = client.fetch::<Cetus>().await?;
println!(
"It is currently {} on cetus. It will be {} in {}",
cetus.state,
cetus.state.opposite(),
cetus.eta()
);

Ok(())
}
```

Expand Down
Loading