Skip to main content

headless_lms_credit_registration/runtime/suotar/
course_codes.rs

1//! Validating course codes with `course-codes/validate`, as many per request as the allowance lets.
2
3use headless_lms_utils::services::suotar::{
4    SuotarEndpoint, ValidateCourseCodeRequestItem, endpoints, new_request_item_id,
5};
6use tracing::Instrument;
7
8use super::SuotarStudyRegistry;
9use super::decode::{course_code_verdict, registry_error};
10use super::encode::course_code_item;
11use super::gate::Exchange;
12use crate::registry::{
13    CourseCode, CourseCodeVerdicts, RegistryError, RegistryOperation, StudyRegistry,
14};
15
16const ENDPOINT: SuotarEndpoint = SuotarEndpoint::ValidateCourseCodes;
17
18pub(super) async fn validate(
19    registry: &mut SuotarStudyRegistry<'_>,
20    codes: &[CourseCode],
21) -> Result<CourseCodeVerdicts, RegistryError> {
22    let mut verdicts = CourseCodeVerdicts::new();
23    let mut unchecked = codes;
24    while !unchecked.is_empty() {
25        let allowance = registry.allowance(RegistryOperation::ValidateCourseCodes);
26        if allowance == 0 {
27            break;
28        }
29        let (chunk, rest) = unchecked.split_at(allowance.min(unchecked.len()));
30        unchecked = rest;
31        let items: Vec<ValidateCourseCodeRequestItem> = chunk
32            .iter()
33            .map(|course_code| course_code_item(course_code, new_request_item_id()))
34            .collect();
35        let request_item_ids: Vec<String> = items
36            .iter()
37            .map(|item| item.request_item_id.clone())
38            .collect();
39        registry.gate.spend(ENDPOINT, items.len());
40        let span = super::request_span(ENDPOINT, items.len());
41        let response = registry
42            .client
43            .post::<endpoints::ValidateCourseCodes>(registry.call_context(Vec::new()), items)
44            .instrument(span)
45            .await;
46        let response = match response {
47            Ok(response) => response,
48            Err(error) => {
49                registry.gate.record(ENDPOINT, Exchange::Refused(&error));
50                debug!(
51                    checked = verdicts.len(),
52                    remaining = unchecked.len(),
53                    "Stopped checking Suotar course codes after a refusal"
54                );
55                return Err(registry_error(&error));
56            }
57        };
58        registry.gate.record(
59            ENDPOINT,
60            Exchange::answered(
61                &response,
62                "Every course code of the batch came back unavailable.",
63            ),
64        );
65        for (course_code, request_item_id) in chunk.iter().zip(&request_item_ids) {
66            if let Some(verdict) = response.item(request_item_id).and_then(course_code_verdict) {
67                verdicts.insert(course_code.clone(), verdict);
68            }
69        }
70    }
71    Ok(verdicts)
72}