|
| 1 | +# Tooling |
| 2 | +We use the basic Rust tooling. Clippy, rustmft, etc. |
| 3 | + |
| 4 | +If you have any recommendations regarding a `rustfmt.toml`, please let us know/make a PR. |
| 5 | + |
| 6 | +# Contributing |
| 7 | +If you have a great idea for a feature, let us know [on the Discord](https://discord.gg/jGZxH9f). |
| 8 | + |
| 9 | +Alternatively, you can open an issue. |
| 10 | + |
| 11 | +# Project structure |
| 12 | +To save you some time, here's a brief explanation of how this project is structured: |
| 13 | + |
| 14 | +There are 2 modules for the "major" things you might want to do, that is querying the [worldstate](https://docs.warframestat.us) and the [market](https://warframe.market/api_docs) (with the `market` feature). |
| 15 | + |
| 16 | +The `worldstate` module is much more developed. This is due to the market API getting a V2 soon. |
| 17 | + |
| 18 | +Since the `market` module is rather small and easy to understand, we'll talk about the `worldstate` module. |
| 19 | + |
| 20 | +## Worldstate module |
| 21 | +All the models are defined via a function-like macro in the `worldstate/models` folder. |
| 22 | + |
| 23 | +### The `model_builder!` macro |
| 24 | +For example, let's look at the definition for `Cetus`: |
| 25 | +```rs |
| 26 | +model_builder! { |
| 27 | + :"The Information about cetus" |
| 28 | + Cetus: "/cetusCycle", |
| 29 | + rt = obj, |
| 30 | + timed = true; |
| 31 | + |
| 32 | + :"The id of the cycle" |
| 33 | + pub id: String, |
| 34 | + |
| 35 | + :"The state of Cetus (day/night)" |
| 36 | + pub state: CetusState, |
| 37 | +} |
| 38 | +``` |
| 39 | +Doc strings are made using the `:"doc here"` syntax. Followed by the `TypeName: "/endpoint_url"`. Said endpoints get concatenated via |
| 40 | +```rs |
| 41 | +concat!("https://api.warframestat.us/pc", $endpoint, "/?language=en") |
| 42 | +``` |
| 43 | +at compile time. This prevents unnecessary allocations. Of course, this doesn't work when you want to query in another language. |
| 44 | + |
| 45 | +When a type has this optional `: "/endpoint"`, it will implement the `Endpoint` trait like so: |
| 46 | + |
| 47 | +```rs |
| 48 | +impl Endpoint for $struct_name { |
| 49 | + fn endpoint_en() -> &'static str { |
| 50 | + concat!("https://api.warframestat.us/pc", $endpoint, "/?language=en") |
| 51 | + } |
| 52 | + |
| 53 | + #[cfg(feature = "multilangual")] |
| 54 | + fn endpoint(language: Language) -> String { |
| 55 | + format!( |
| 56 | + "https://api.warframestat.us/pc{}/?language={}", |
| 57 | + $endpoint, |
| 58 | + String::from(language) |
| 59 | + ) |
| 60 | + } |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +This is followed by an `rt = obj/arr`, which tells the model in which format it is returned in. |
| 65 | +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`. |
| 66 | + |
| 67 | +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. |
| 68 | + |
| 69 | +### Putting it all together |
| 70 | +To understand this, lets look at the `Queryable` trait first: |
| 71 | +```rs |
| 72 | +pub trait Queryable: Endpoint { |
| 73 | + type Return: DeserializeOwned; |
| 74 | + fn query( |
| 75 | + request_executor: &reqwest::Client, |
| 76 | + ) -> impl std::future::Future<Output = Result<Self::Return, ApiError>> + Send { |
| 77 | + async { |
| 78 | + Ok(request_executor |
| 79 | + .get(Self::endpoint_en()) |
| 80 | + .send() |
| 81 | + .await? |
| 82 | + .json::<Self::Return>() |
| 83 | + .await?) |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + #[cfg(feature = "multilangual")] |
| 88 | + fn query_with_language( |
| 89 | + ... |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +if a model has the endpoint signature (`: "/endpoint"`), the `Queryable` trait will be implemented by the macro. |
| 94 | +Based on the `rt`, the `type Return` will either be `Self`, or `Vec<Self>`. |
| 95 | + |
| 96 | +Now, all the `Client`'s `fetch` does: |
| 97 | +```rs |
| 98 | +impl Client { |
| 99 | + pub async fn fetch<T>(&self) -> Result<T::Return, ApiError> |
| 100 | + where |
| 101 | + T: Queryable, |
| 102 | + { |
| 103 | + <T as Queryable>::query(&self.session).await |
| 104 | + } |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +This means, depending on the type queried, you get a `Vec<Model>`, or a single `Model`. |
| 109 | + |
| 110 | +E.g. |
| 111 | +```rs |
| 112 | +let fissures: Vec<Fissure> = client.fetch<Fissure>().await?; |
| 113 | +let cetus: Cetus = client.fetch<Cetus>().await?; |
| 114 | +``` |
| 115 | + |
| 116 | +If you have any questions, feel free to ask on the discord, or open an issue. |
0 commit comments