1use crate::groups::Page;
2use crate::prelude::*;
3use uuid::Uuid;
4
5pub struct ParagonHubGroup<'a> {
7 pub(crate) esi: &'a Esi,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum ListingState {
14 Listed,
16 SoldOut,
18 Expired,
20 Removed,
22 #[serde(other)]
24 Unrecognized,
25}
26
27#[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#[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 pub seller_id: i64,
48 pub skinr_id: String,
49 pub state: ListingState,
50}
51
52impl ParagonHubGroup<'_> {
53 api_get!(
54 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#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
97pub struct ListingTarget {
98 pub character_id: Option<i64>,
100 pub corporation_id: Option<i64>,
102 pub alliance_id: Option<i64>,
104 pub public: Option<bool>,
106}
107
108#[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 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 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 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 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}