Skip to main content

esi_openapi/groups/
paragon_hub.rs

1use crate::groups::Page;
2use crate::prelude::*;
3use uuid::Uuid;
4
5/// Endpoints for Paragon Hub
6pub struct ParagonHubGroup<'a> {
7    pub(crate) esi: &'a Esi,
8}
9
10/// The state of a SKINR listing.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum ListingState {
14    /// For sale.
15    Listed,
16    /// Sold out.
17    SoldOut,
18    /// Expired.
19    Expired,
20    /// Removed by the seller.
21    Removed,
22    /// A state this version of the crate does not know about.
23    #[serde(other)]
24    Unrecognized,
25}
26
27/// The price of a listing, in ISK or in PLEX.
28#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
29#[serde(untagged)]
30#[allow(missing_docs)]
31pub enum ListingPrice {
32    Isk { isk: f64 },
33    Plex { plex: i64 },
34}
35
36/// A SKINR listing.
37#[derive(Debug, Clone, PartialEq, Deserialize)]
38#[allow(missing_docs)]
39pub struct SkinrListing {
40    pub created: String,
41    pub expires: String,
42    pub id: Uuid,
43    pub last_modified: String,
44    pub price: ListingPrice,
45    pub quantity: i64,
46    /// Character ID of the seller.
47    pub seller_id: i64,
48    pub skinr_id: String,
49    pub state: ListingState,
50}
51
52impl ParagonHubGroup<'_> {
53    api_get!(
54        /// List SKINR listings. Use the cursor of the returned page as `after` or
55        /// `before` to walk through the list.
56        get_skinr_listings,
57        "GetParagonHubSkinr",
58        RequestType::Public,
59        Page<SkinrListing>,
60        ;
61        Optional(after: String) => "after",
62        Optional(before: String) => "before",
63        Optional(limit: i64) => "limit"
64    );
65}
66
67#[cfg(test)]
68mod tests {
69    use super::{ListingPrice, ListingState};
70    use crate::groups::{Page, SkinrListing};
71
72    #[test]
73    fn test_parse_listings_with_both_price_kinds() {
74        let json = r#"{"listings": [
75            {"created": "2026-10-01T00:00:00Z", "expires": "2026-11-01T00:00:00Z",
76             "id": "3868eaed-8278-4cb7-9709-7d7de9c20dc7", "last_modified": "2026-10-02T00:00:00Z",
77             "price": {"isk": 1500000.5}, "quantity": 1, "seller_id": 2112000000,
78             "skinr_id": "abc", "state": "sold_out"},
79            {"created": "2026-10-01T00:00:00Z", "expires": "2026-11-01T00:00:00Z",
80             "id": "3868eaed-8278-4cb7-9709-7d7de9c20dc8", "last_modified": "2026-10-02T00:00:00Z",
81             "price": {"plex": 250}, "quantity": 3, "seller_id": 2112000001,
82             "skinr_id": "def", "state": "listed"}
83        ]}"#;
84        let page: Page<SkinrListing> = serde_json::from_str(json).unwrap();
85        assert!(page.cursor.is_none());
86        assert_eq!(page.items[0].state, ListingState::SoldOut);
87        assert!(matches!(page.items[0].price, ListingPrice::Isk { .. }));
88        assert!(matches!(
89            page.items[1].price,
90            ListingPrice::Plex { plex: 250 }
91        ));
92    }
93}
94
95/// Who a SKINR listing is offered to: only one field is set.
96#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
97pub struct ListingTarget {
98    /// Offered to one character.
99    pub character_id: Option<i64>,
100    /// Offered to one corporation.
101    pub corporation_id: Option<i64>,
102    /// Offered to one alliance.
103    pub alliance_id: Option<i64>,
104    /// Offered to everyone.
105    pub public: Option<bool>,
106}
107
108/// A SKINR listing of a character, including who it is offered to.
109#[derive(Debug, Clone, PartialEq, Deserialize)]
110#[allow(missing_docs)]
111pub struct CharacterSkinrListing {
112    pub created: String,
113    pub expires: String,
114    pub id: Uuid,
115    pub last_modified: String,
116    pub price: ListingPrice,
117    pub quantity: i64,
118    pub seller_id: i64,
119    pub skinr_id: String,
120    pub state: ListingState,
121    pub target: ListingTarget,
122}
123
124impl ParagonHubGroup<'_> {
125    api_get!(
126        /// List the SKINR listings of a character.
127        get_character_skinr_listings,
128        "GetCharactersParagonHubSkinr",
129        RequestType::Authenticated,
130        Page<CharacterSkinrListing>,
131        (character_id: i64) => "{character_id}";
132        Optional(after: String) => "after",
133        Optional(before: String) => "before",
134        Optional(limit: i64) => "limit"
135    );
136
137    api_get!(
138        /// List the SKINR listings offered to an alliance.
139        get_alliance_skinr_listings,
140        "GetParagonHubSkinrAlliances",
141        RequestType::Authenticated,
142        Page<SkinrListing>,
143        (alliance_id: i64) => "{alliance_id}";
144        Optional(after: String) => "after",
145        Optional(before: String) => "before",
146        Optional(limit: i64) => "limit"
147    );
148
149    api_get!(
150        /// List the SKINR listings offered to a character.
151        get_skinr_listings_for_character,
152        "GetParagonHubSkinrCharacters",
153        RequestType::Authenticated,
154        Page<SkinrListing>,
155        (character_id: i64) => "{character_id}";
156        Optional(after: String) => "after",
157        Optional(before: String) => "before",
158        Optional(limit: i64) => "limit"
159    );
160
161    api_get!(
162        /// List the SKINR listings offered to a corporation.
163        get_corporation_skinr_listings,
164        "GetParagonHubSkinrCorporations",
165        RequestType::Authenticated,
166        Page<SkinrListing>,
167        (corporation_id: i64) => "{corporation_id}";
168        Optional(after: String) => "after",
169        Optional(before: String) => "before",
170        Optional(limit: i64) => "limit"
171    );
172}
173
174#[cfg(test)]
175mod target_tests {
176    use super::*;
177
178    #[test]
179    fn test_parse_character_listing_target() {
180        let page: Page<CharacterSkinrListing> = serde_json::from_str(
181            r#"{"listings":[{"created":"a","expires":"b","id":"67e6b7a0-6f58-4b5b-a2c4-4f3a1f0f2b11",
182            "last_modified":"c","price":{"plex":5},"quantity":1,"seller_id":2,"skinr_id":"s",
183            "state":"listed","target":{"corporation_id":9}}]}"#,
184        )
185        .unwrap();
186        assert_eq!(page.items[0].target.corporation_id, Some(9));
187        assert!(page.cursor.is_none());
188    }
189}