Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/mock-scaleway/src/catalog.rs

//! **The product catalog** — what `GET /products/servers` and `GET /products/volumes` answer.
//!
//! The server rows carry Scaleway's own field names and units (`ram` in BYTES, prices in EUR
//! excluding VAT as floats, `hourly_price` and `monthly_price`). The numbers are close to the
//! public list of fr-par-1 in October 2026 and are the MOCK's: nothing reads them as a price
//! anybody pays.
//!
//! Volumes: the real `GET /products/volumes` answers each volume type's display name,
//! capabilities and size constraints, and NO price (block storage is priced in the Block
//! Storage and product catalogs). The mock adds `hourly_price_per_gb` to each row so a guest's
//! `price` has one place to read; that one field is the mock's, and the module says so.

use serde_json::{json, Value};

/// `(commercial_type, ncpus, ram GiB, hourly EUR, monthly EUR, arch)`.
pub const SERVERS: &[(&str, u32, u64, f64, f64)] = &[
    ("DEV1-S", 2, 2, 0.0088, 6.42),
    ("DEV1-M", 3, 4, 0.0198, 14.45),
    ("DEV1-L", 4, 8, 0.042, 30.66),
    ("DEV1-XL", 4, 12, 0.063, 45.99),
    ("PRO2-XXS", 2, 8, 0.055, 40.15),
    ("PRO2-XS", 4, 16, 0.11, 80.3),
    ("PRO2-S", 8, 32, 0.219, 159.87),
    ("PRO2-M", 16, 64, 0.438, 319.74),
];

/// `(volume_type, display name, hourly EUR per GB, can resize, min bytes, max bytes)`.
pub const VOLUMES: &[(&str, &str, f64, bool, u64, u64)] = &[
    (
        "l_ssd",
        "Local SSD",
        0.0,
        false,
        1_000_000_000,
        600_000_000_000,
    ),
    (
        "b_ssd",
        "Block SSD",
        0.000_11,
        true,
        1_000_000_000,
        10_000_000_000_000,
    ),
    (
        "sbs_volume",
        "Block Storage (SBS)",
        0.000_118,
        true,
        1_000_000_000,
        10_000_000_000_000,
    ),
];

/// The snapshot price per GB-hour (what a backup costs while it is kept).
pub const SNAPSHOT_HOURLY_PER_GB: f64 = 0.000_046_6;

pub const GB: u64 = 1_000_000_000;
pub const GIB: u64 = 1024 * 1024 * 1024;

pub fn server_row(t: &str) -> Option<(u32, u64)> {
    SERVERS.iter().find(|r| r.0 == t).map(|r| (r.1, r.2))
}

pub fn volume_row(t: &str) -> Option<&'static (&'static str, &'static str, f64, bool, u64, u64)> {
    VOLUMES.iter().find(|r| r.0 == t)
}

/// `GET /products/servers`.
pub fn servers_json() -> Value {
    let mut m = serde_json::Map::new();
    for (t, cpus, ram, hourly, monthly) in SERVERS {
        m.insert(
            (*t).to_string(),
            json!({
                "alt_names": [],
                "arch": "x86_64",
                "ncpus": cpus,
                "ram": ram * GIB,
                "gpu": 0,
                "baremetal": false,
                "hourly_price": hourly,
                "monthly_price": monthly,
                "volumes_constraint": { "min_size": 0, "max_size": 0 },
                "per_volume_constraint": { "l_ssd": { "min_size": 1_000_000_000u64, "max_size": 600_000_000_000u64 } },
                "network": { "sum_internal_bandwidth": 400_000_000u64, "sum_internet_bandwidth": 400_000_000u64, "ipv6_support": true, "interfaces": [] },
                "block_bandwidth": 1_073_741_824u64,
                "end_of_service": false,
            }),
        );
    }
    json!({ "servers": m })
}

/// `GET /products/volumes` (plus the mock's own `hourly_price_per_gb`).
pub fn volumes_json() -> Value {
    let mut m = serde_json::Map::new();
    for (t, name, hourly, resize, min, max) in VOLUMES {
        m.insert(
            (*t).to_string(),
            json!({
                "display_name": name,
                "capabilities": { "snapshot": true, "block_snapshot": *t != "l_ssd" },
                "constraint": { "min": min, "max": max },
                "can_resize": resize,
                "hourly_price_per_gb": hourly,
            }),
        );
    }
    json!({ "volumes": m, "snapshot_hourly_price_per_gb": SNAPSHOT_HOURLY_PER_GB })
}