Skip to main content

esi_openapi/groups/
freelance_jobs.rs

1use crate::groups::Page;
2use crate::prelude::*;
3use uuid::Uuid;
4
5/// Endpoints for Freelance Jobs
6pub struct FreelanceJobsGroup<'a> {
7    pub(crate) esi: &'a Esi,
8}
9
10/// The state of a freelance job.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
12pub enum JobState {
13    /// The state is not specified.
14    Unspecified,
15    /// The job is open.
16    Active,
17    /// The job was closed.
18    Closed,
19    /// The job was completed.
20    Completed,
21    /// The job expired.
22    Expired,
23    /// The job was deleted.
24    Deleted,
25    /// A state this version of the crate does not know about.
26    #[serde(other)]
27    Unrecognized,
28}
29
30/// The career a freelance job belongs to.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
32pub enum JobCareer {
33    /// The career is not specified.
34    Unspecified,
35    /// Explorer.
36    Explorer,
37    /// Industrialist.
38    Industrialist,
39    /// Enforcer.
40    Enforcer,
41    /// Soldier of Fortune.
42    #[serde(rename = "Soldier of Fortune")]
43    SoldierOfFortune,
44    /// A career this version of the crate does not know about.
45    #[serde(other)]
46    Unrecognized,
47}
48
49/// Progress towards completing a job.
50#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
51#[allow(missing_docs)]
52pub struct JobProgress {
53    pub current: i64,
54    pub desired: i64,
55}
56
57/// The ISK reward of a job.
58#[derive(Debug, Clone, PartialEq, Deserialize)]
59#[allow(missing_docs)]
60pub struct JobReward {
61    pub initial: f64,
62    pub remaining: f64,
63}
64
65/// A freelance job as listed by [`FreelanceJobsGroup::list`].
66#[derive(Debug, Clone, PartialEq, Deserialize)]
67#[allow(missing_docs)]
68pub struct FreelanceJobSummary {
69    pub id: Uuid,
70    pub last_modified: String,
71    pub name: String,
72    pub progress: JobProgress,
73    pub reward: Option<JobReward>,
74    pub state: JobState,
75}
76
77/// A solar system a job is broadcast in.
78#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
79#[allow(missing_docs)]
80pub struct BroadcastLocation {
81    /// Solar system ID.
82    pub id: i64,
83    pub name: String,
84}
85
86/// Age restrictions on who can take a job.
87#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
88#[allow(missing_docs)]
89pub struct JobRestrictions {
90    pub maximum_age: Option<i64>,
91    pub minimum_age: Option<i64>,
92}
93
94/// Who can see and take a job.
95#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
96#[allow(missing_docs)]
97pub struct AccessAndVisibility {
98    pub acl_protected: bool,
99    pub broadcast_locations: Option<Vec<BroadcastLocation>>,
100    pub restrictions: Option<JobRestrictions>,
101}
102
103/// How a job is evaluated. `parameters` depends on `method`, so it is kept as raw JSON.
104#[derive(Debug, Clone, PartialEq, Deserialize)]
105#[allow(missing_docs)]
106pub struct JobConfiguration {
107    pub method: String,
108    pub parameters: serde_json::Value,
109    pub version: i64,
110}
111
112/// Limits on participants' contributions.
113#[derive(Debug, Clone, PartialEq, Deserialize)]
114#[allow(missing_docs)]
115pub struct JobContribution {
116    pub contribution_per_participant_limit: Option<i64>,
117    pub max_committed_participants: i64,
118    pub reward_per_contribution: Option<f64>,
119    pub submission_limit: Option<i64>,
120    pub submission_multiplier: Option<f64>,
121}
122
123/// The character that created a job.
124#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
125#[allow(missing_docs)]
126pub struct JobCreatorCharacter {
127    pub id: i64,
128    pub name: String,
129}
130
131/// The corporation that created a job.
132#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
133#[allow(missing_docs)]
134pub struct JobCreatorCorporation {
135    pub id: i64,
136    pub name: String,
137}
138
139/// Who created a job.
140#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
141#[allow(missing_docs)]
142pub struct JobCreator {
143    pub character: JobCreatorCharacter,
144    pub corporation: JobCreatorCorporation,
145}
146
147/// Descriptive details of a job.
148#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
149#[allow(missing_docs)]
150pub struct JobDetails {
151    pub career: JobCareer,
152    pub created: String,
153    pub creator: JobCreator,
154    pub description: String,
155    pub expires: Option<String>,
156    pub finished: Option<String>,
157}
158
159/// Full information about a freelance job.
160#[derive(Debug, Clone, PartialEq, Deserialize)]
161#[allow(missing_docs)]
162pub struct FreelanceJob {
163    pub access_and_visibility: AccessAndVisibility,
164    pub configuration: JobConfiguration,
165    pub contribution: Option<JobContribution>,
166    pub details: JobDetails,
167    pub id: Uuid,
168    pub last_modified: String,
169    pub name: String,
170    pub progress: JobProgress,
171    pub reward: Option<JobReward>,
172    pub state: JobState,
173}
174
175impl FreelanceJobsGroup<'_> {
176    api_get!(
177        /// List freelance jobs. Use the cursor of the returned page as `after` or
178        /// `before` to walk through the list; `corporation_id` filters by creator.
179        list,
180        "GetFreelanceJobsListing",
181        RequestType::Public,
182        Page<FreelanceJobSummary>,
183        ;
184        Optional(after: String) => "after",
185        Optional(before: String) => "before",
186        Optional(limit: i64) => "limit",
187        Optional(corporation_id: i64) => "corporation_id"
188    );
189
190    api_get!(
191        /// Get the details of a freelance job.
192        get_job,
193        "GetFreelanceJobsDetail",
194        RequestType::Public,
195        FreelanceJob,
196        (job_id: Uuid) => "{job_id}"
197    );
198}
199
200#[cfg(test)]
201mod tests {
202    use super::JobState;
203    use crate::groups::{FreelanceJobSummary, Page};
204
205    #[test]
206    fn test_parse_page_with_unknown_state() {
207        let json = r#"{
208            "cursor": {"after": "abc"},
209            "freelance_jobs": [{
210                "id": "3868eaed-8278-4cb7-9709-7d7de9c20dc7",
211                "last_modified": "2026-10-01T00:00:00Z",
212                "name": "Haul",
213                "progress": {"current": 1, "desired": 10},
214                "state": "Archived"
215            }]
216        }"#;
217        let page: Page<FreelanceJobSummary> = serde_json::from_str(json).unwrap();
218        assert_eq!(page.items[0].state, JobState::Unrecognized);
219        assert!(page.items[0].reward.is_none());
220        assert_eq!(page.cursor.unwrap().after.as_deref(), Some("abc"));
221    }
222}
223
224/// The state of a character's participation in a freelance job.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
226pub enum ParticipationState {
227    /// No state specified.
228    Unspecified,
229    /// Committed to the job.
230    Committed,
231    /// Kicked from the job.
232    Kicked,
233    /// Resigned from the job.
234    Resigned,
235    /// A state this version of the crate does not know about.
236    #[serde(other)]
237    Unrecognized,
238}
239
240/// A character's participation in a freelance job.
241#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
242#[allow(missing_docs)]
243pub struct JobParticipation {
244    pub contributed: i64,
245    pub last_modified: String,
246    pub state: ParticipationState,
247}
248
249/// A participant of a corporation's freelance job.
250#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
251#[allow(missing_docs)]
252pub struct JobParticipant {
253    pub contributed: i64,
254    /// Character ID of the participant.
255    pub id: i64,
256    pub name: String,
257    pub state: ParticipationState,
258}
259
260#[derive(Debug, Deserialize)]
261struct CharacterFreelanceJobs {
262    freelance_jobs: Vec<FreelanceJobSummary>,
263}
264
265impl FreelanceJobsGroup<'_> {
266    /// List the freelance jobs a character takes part in.
267    pub async fn get_character_jobs(
268        &self,
269        character_id: i64,
270    ) -> EsiResult<Vec<FreelanceJobSummary>> {
271        let path = self
272            .esi
273            .get_endpoint_for_op_id("GetCharactersFreelanceJobsListing")?
274            .replace("{character_id}", &character_id.to_string());
275        let wrapper: CharacterFreelanceJobs = self
276            .esi
277            .query("GET", RequestType::Authenticated, &path, None, None)
278            .await?;
279        Ok(wrapper.freelance_jobs)
280    }
281
282    api_get!(
283        /// Get the participation of a character in a freelance job.
284        get_character_participation,
285        "GetCharactersFreelanceJobsParticipation",
286        RequestType::Authenticated,
287        JobParticipation,
288        (character_id: i64) => "{character_id}",
289        (job_id: Uuid) => "{job_id}"
290    );
291
292    api_get!(
293        /// List the freelance jobs of a corporation (requires the Project_Manager role).
294        list_corporation_jobs,
295        "GetCorporationsFreelanceJobsListing",
296        RequestType::Authenticated,
297        Page<FreelanceJobSummary>,
298        (corporation_id: i64) => "{corporation_id}";
299        Optional(after: String) => "after",
300        Optional(before: String) => "before",
301        Optional(limit: i64) => "limit"
302    );
303
304    api_get!(
305        /// List the participants of a corporation's freelance job (requires the Project_Manager role).
306        list_corporation_job_participants,
307        "GetCorporationsFreelanceJobsParticipants",
308        RequestType::Authenticated,
309        Page<JobParticipant>,
310        (corporation_id: i64) => "{corporation_id}",
311        (job_id: Uuid) => "{job_id}";
312        Optional(after: String) => "after",
313        Optional(before: String) => "before",
314        Optional(limit: i64) => "limit"
315    );
316}