dimescheduler 0.1.0

Official Rust client library for the Dime.Scheduler API.
Documentation
//! Every path in spec/openapi.json must be exposed by an accessor, or listed
//! as intentionally uncovered with a reason. Ported from
//! packages/go/client/coverage_test.go.

use std::collections::BTreeMap;

const SPEC: &str = "../../spec/openapi.json"; // cargo test runs with cwd = packages/rust

fn covered_paths() -> BTreeMap<&'static str, &'static str> {
    BTreeMap::from([
        ("/actionUri", "action_uris"),
        ("/appointment", "appointments"),
        ("/appointment/{id}", "appointments.get_by_id"),
        ("/appointmentCategory", "appointment_categories"),
        ("/appointmentcontent", "appointment_contents"),
        ("/appointmentContainer", "appointment_containers"),
        ("/appointmentDependency", "appointment_dependencies"),
        ("/appointmentField", "appointment_fields"),
        ("/appointmentFieldValue", "appointment_field_values"),
        ("/appointmentImportance", "appointment_importances"),
        ("/appointmentLocked", "appointment_locked"),
        (
            "/appointmentPlanningQuantity",
            "appointment_planning_quantities",
        ),
        ("/appointmentRecurring", "recurring_appointments"),
        ("/appointmentTimeMarker", "appointment_time_markers"),
        ("/appointmentUri", "appointment_uris"),
        ("/assignment", "assignments"),
        ("/calendar", "calendars"),
        ("/caption", "captions"),
        ("/category", "categories"),
        ("/connector", "connectors"),
        ("/container", "containers"),
        ("/filterGroup", "filter_groups"),
        ("/filterValue", "filter_values"),
        ("/geocode", "geocoding"),
        ("/import", "imports.run"),
        ("/import/setup", "imports.setup"),
        ("/job", "jobs"),
        ("/job/{jobNo}", "jobs.get"),
        ("/message", "messages"),
        ("/notification", "notifications"),
        ("/optimization/dailyRoute", "optimization.daily_route"),
        ("/optimization/fieldService", "optimization.field_service"),
        (
            "/optimization/professionalServices",
            "optimization.professional_services",
        ),
        ("/pin", "pins"),
        ("/projectMember", "project_members"),
        ("/recommendation", "recommendation.get"),
        ("/recommendation/scorers", "recommendation.scorers"),
        ("/resource", "resources"),
        ("/resourceCalendar", "resource_calendars"),
        ("/resourceCapacity", "resource_capacities"),
        ("/resourceCertificate", "resource_certificates"),
        ("/resourceFilterValue", "resource_filter_values"),
        ("/resourceGpsTracking", "resource_gps_trackings"),
        ("/resourcetype", "resource_types"),
        ("/resourceUri", "resource_uris"),
        ("/tag", "tags"),
        ("/task", "tasks"),
        ("/taskAssignment", "task_assignments"),
        ("/taskBudgetEntry", "task_budget_entries"),
        ("/taskContainer", "task_containers"),
        ("/taskDependency", "task_dependencies"),
        ("/taskFilterValue", "task_filter_values"),
        ("/taskLocked", "task_locked"),
        ("/taskUri", "task_uris"),
        ("/timeEntry", "time_entries"),
        ("/timeMarker", "time_markers"),
        ("/timeSession", "time_sessions"),
        ("/user", "users"),
    ])
}

fn intentionally_uncovered() -> BTreeMap<&'static str, &'static str> {
    BTreeMap::from([(
        "/tokens",
        "auth/login flow; the SDK takes a pre-issued API key",
    )])
}

#[test]
fn every_spec_path_is_accounted_for() {
    let raw = std::fs::read_to_string(SPEC).expect("read spec");
    let spec: serde_json::Value = serde_json::from_str(&raw).expect("parse spec");
    let paths = spec["paths"].as_object().expect("paths object");
    let covered = covered_paths();
    let uncovered = intentionally_uncovered();

    let missing: Vec<&str> = paths
        .keys()
        .map(String::as_str)
        .filter(|p| !covered.contains_key(p) && !uncovered.contains_key(p))
        .collect();
    let stale: Vec<&str> = covered
        .keys()
        .chain(uncovered.keys())
        .copied()
        .filter(|p| !paths.contains_key(*p))
        .collect();

    assert!(
        missing.is_empty(),
        "spec paths without an accessor: {missing:?}. Add one on DimeSchedulerClient and list it in covered_paths."
    );
    assert!(
        stale.is_empty(),
        "paths listed here but gone from the spec: {stale:?}"
    );
    for p in covered.keys() {
        assert!(
            !uncovered.contains_key(p),
            "{p} is both covered and intentionally uncovered"
        );
    }
}