1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! Types related to list of solutions returned by the [Exercism website](https://exercism.org) v2 API.

pub(crate) mod detail;

use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, Display, IntoStaticStr};

use crate::api::v2::solution::{MentoringStatus, Solution, Status};
use crate::api::v2::tests;

/// Filters that can be applied when fetching solutions from the
/// [Exercism website](https://exercism.org) v2 API.
#[derive(Debug, Clone, Default, Builder)]
#[builder(
    derive(Debug),
    default,
    setter(strip_option),
    build_fn(private, name = "fallible_build", error = "detail::FiltersBuilderError")
)]
pub struct Filters<'a> {
    /// Criteria used to filter solutions.
    ///
    /// Applied to both the solution's exercise's title or its track's title.
    #[builder(setter(into))]
    pub criteria: Option<&'a str>,

    /// Name (e.g. slug) of the track containing solution's exercise.
    #[builder(setter(into))]
    pub track: Option<&'a str>,

    /// Solution [status](Status).
    pub status: Option<Status>,

    /// Solution's [mentoring status](MentoringStatus).
    pub mentoring_status: Option<MentoringStatus>,

    /// Whether the solution is out-of-date or not.
    ///
    /// If set, only solutions that are out-of-date (`true`) or up-to-date (`false`)
    /// will be included.
    pub is_out_of_date: Option<bool>,

    /// Possible status of the solution's published iteration's tests.
    ///
    /// # Notes
    ///
    /// The difference between a "head test run" and a normal test run is somewhat explained
    /// in the Exercism website source code [here](https://github.com/exercism/website/blob/main/app/models/submission.rb).
    #[builder(setter(into, each(name = "published_iteration_tests_status")))]
    pub published_iteration_tests_statuses: Vec<tests::Status>,

    /// Possible status of the solution's published iteration's head tests.
    ///
    /// Corresponds to the value found in [`Solution::published_iteration_head_tests_status`](crate::api::v2::solution::Solution::published_iteration_head_tests_status).
    ///
    /// # Notes
    ///
    /// The difference between a "head test run" and a normal test run is somewhat explained
    /// in the Exercism website source code [here](https://github.com/exercism/website/blob/main/app/models/submission.rb).
    #[builder(setter(into, each(name = "published_iteration_head_tests_status")))]
    pub published_iteration_head_tests_statuses: Vec<tests::Status>,
}

impl<'a> Filters<'a> {
    /// Returns a builder for the [`Filters`] type.
    pub fn builder() -> FiltersBuilder<'a> {
        FiltersBuilder::default()
    }
}

impl<'a> FiltersBuilder<'a> {
    /// Adds a filter to only return out-of-date solutions.
    pub fn out_of_date(&mut self) -> &mut Self {
        self.is_out_of_date(true)
    }

    /// Adds a filter to only return up-to-date solutions.
    pub fn up_to_date(&mut self) -> &mut Self {
        self.is_out_of_date(false)
    }

    /// Builds a new [`Filters`].
    pub fn build(&self) -> Filters<'a> {
        self.fallible_build()
            .expect("All fields should have had default values")
    }
}

/// Paging information when performing paged queries for solutions to the
/// [Exercism website](https://exercism.org) v2 API.
#[derive(Debug, Copy, Clone)]
pub struct Paging {
    /// Page number (1-based).
    pub page: i64,

    /// Number of solutions to return in each page.
    ///
    /// If not specified, the default value (documented [here](https://github.com/exercism/website/blob/main/app/commands/solution/search_user_solutions.rb#L1))
    /// will be used.
    pub per_page: Option<i64>,
}

impl Paging {
    /// Returns [`Paging`] information for the given `page`.
    ///
    /// The page size will be left blank, so the default value will be used. The page size
    /// can also be set via [`and_per_page`](Self::and_per_page).
    pub fn for_page(page: i64) -> Self {
        Self { page, per_page: None }
    }

    /// Further specifies the page size for the request.
    ///
    /// # Examples
    ///
    /// ```
    /// use mini_exercism::api::v2::solutions::Paging;
    ///
    /// let paging = Paging::for_page(1).and_per_page(10);
    /// assert_eq!(1, paging.page);
    /// assert_eq!(Some(10), paging.per_page);
    /// ```
    pub fn and_per_page(self, per_page: i64) -> Self {
        Self { page: self.page, per_page: Some(per_page) }
    }
}

/// Possible ways to sort solutions returned by the [Exercism website](https://exercism.org) v2 API.
#[derive(
    Debug,
    Default,
    Copy,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Display,
    AsRefStr,
    IntoStaticStr,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum SortOrder {
    /// Return solutions from newest to oldest.
    NewestFirst,

    /// Return solutions from oldest to newest.
    OldestFirst,

    /// Return solutions based on number of stars (descending).
    ///
    /// The value used is the same as [`Solution::num_stars`](crate::api::v2::solution::Solution::num_stars).
    #[default]
    MostStarred,
}

/// Response to a query for solutions on the [Exercism website](https://exercism.org) v2 API.
/// Responses are paginated, so this only returns one page of results.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Response {
    /// List of solutions in the current page.
    pub results: Vec<Solution>,

    /// Metadata containing paging information.
    pub meta: ResponseMeta,
}

/// Metadata attached to a response to a query for solutions on the [Exercism website](https://exercism.org) v2 API.
/// Contains paging information.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResponseMeta {
    /// Current page number (1-based).
    pub current_page: i64,

    /// Total number of solutions matching the query.
    pub total_count: i64,

    /// Total number of pages that could be returned for the query.
    pub total_pages: i64,
}