#[cfg(feature = "ffi")]
pub mod ffi;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::panic::{catch_unwind, AssertUnwindSafe};
pub const BRIDGE_API_VERSION: u32 = 1;
pub const MAX_REQUEST_BYTES: usize = 1024 * 1024;
const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");
fn command_names() -> Vec<&'static str> {
let mut names = vec![
"meta.capabilities",
"meta.version",
"solve",
"card.come_ups",
"card.range_table",
"card.wind",
];
#[cfg(feature = "pdf")]
names.push("card.pdf");
names.extend(["profile.validate", "profile.normalize"]);
#[cfg(feature = "profile-import")]
names.push("profile.import_a7p");
names.extend([
"true.fit",
"true.wind",
"true.tall_target",
"true.dsf",
"true.plan",
"true.dial_plan",
]);
#[cfg(not(target_arch = "wasm32"))]
names.push("bc5d.info");
names
}
fn compiled_features() -> Vec<&'static str> {
[
("pdf", cfg!(feature = "pdf")),
("profile-import", cfg!(feature = "profile-import")),
("online", cfg!(feature = "online")),
]
.iter()
.filter(|(_, enabled)| *enabled)
.map(|(name, _)| *name)
.collect()
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct BridgeRequest {
api_version: u32,
command: String,
#[serde(default)]
request: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BridgeErrorCode {
InvalidJson,
UnsupportedApiVersion,
UnknownCommand,
InvalidRequest,
ResourceLimit,
CommandFailed,
InternalError,
}
fn success(command: &str, result: Value) -> String {
serialize_envelope(&json!({
"ok": true,
"api_version": BRIDGE_API_VERSION,
"engine_version": ENGINE_VERSION,
"command": command,
"result": result,
}))
}
fn error(code: BridgeErrorCode, message: impl Into<String>, details: Option<Value>) -> String {
let mut error = json!({
"code": code,
"message": message.into(),
});
if let Some(details) = details {
error["details"] = details;
}
serialize_envelope(&json!({
"ok": false,
"api_version": BRIDGE_API_VERSION,
"engine_version": ENGINE_VERSION,
"error": error,
}))
}
fn serialize_envelope(value: &Value) -> String {
serde_json::to_string(value).unwrap_or_else(|_| {
format!(
r#"{{"ok":false,"api_version":{BRIDGE_API_VERSION},"engine_version":"{ENGINE_VERSION}","error":{{"code":"internal_error","message":"bridge response serialization failed"}}}}"#
)
})
}
pub fn bridge_call(request_json: &str) -> String {
let guarded = catch_unwind(AssertUnwindSafe(|| dispatch(request_json)));
guarded.unwrap_or_else(|_| {
error(
BridgeErrorCode::InternalError,
"bridge command failed unexpectedly",
None,
)
})
}
fn dispatch(request_json: &str) -> String {
if request_json.len() > MAX_REQUEST_BYTES {
return error(
BridgeErrorCode::ResourceLimit,
format!("bridge request exceeds the {MAX_REQUEST_BYTES}-byte limit"),
None,
);
}
let request: BridgeRequest = match serde_json::from_str(request_json) {
Ok(request) => request,
Err(err) => {
return error(
BridgeErrorCode::InvalidJson,
format!("bridge request is not a valid envelope: {err}"),
None,
)
}
};
if request.api_version != BRIDGE_API_VERSION {
return error(
BridgeErrorCode::UnsupportedApiVersion,
format!(
"unsupported api_version {}; this build speaks {BRIDGE_API_VERSION}",
request.api_version
),
None,
);
}
match request.command.as_str() {
"meta.capabilities" => success(
"meta.capabilities",
json!({
"engine_version": ENGINE_VERSION,
"bridge_api_version": BRIDGE_API_VERSION,
"commands": command_names(),
"features": compiled_features(),
"solve_schema_version": crate::solve_json::SOLVE_JSON_SCHEMA_VERSION_V1,
}),
),
"meta.version" => success(
"meta.version",
json!({ "engine_version": ENGINE_VERSION }),
),
"solve" => run_solve(&request.request),
"card.come_ups" => {
run_service(&request.request, "card.come_ups", crate::card_service::come_ups_v1)
}
"card.range_table" => run_service(
&request.request,
"card.range_table",
crate::card_service::range_table_v1,
),
"card.wind" => run_service(&request.request, "card.wind", crate::card_service::wind_card_v1),
#[cfg(feature = "pdf")]
"card.pdf" => run_card_pdf(&request.request),
"profile.validate" => run_profile_validate(&request.request),
"profile.normalize" => run_profile_normalize(&request.request),
#[cfg(feature = "profile-import")]
"profile.import_a7p" => run_profile_import_a7p(&request.request),
"true.fit" => run_service(
&request.request,
"true.fit",
crate::truing_uncertainty::run_uncertainty_truing_v1,
),
"true.wind" => run_service(
&request.request,
"true.wind",
crate::truing_wind::solve_wind_truing,
),
"true.tall_target" => run_service(
&request.request,
"true.tall_target",
crate::truing_service::tall_target_v1,
),
"true.dsf" => run_service_detailed(
&request.request,
"true.dsf",
crate::truing_service::derive_dsf_point_v1,
crate::truing_service::DsfServiceErrorV1::failure_details,
),
"true.plan" => run_service_detailed(
&request.request,
"true.plan",
crate::truing_plan::plan_truing_experiment_v1,
crate::truing_plan::TruingPlanErrorV1::failure_details,
),
"true.dial_plan" => run_service_detailed(
&request.request,
"true.dial_plan",
crate::truing_service::dial_plan_v1,
crate::optic::OpticError::failure_details,
),
#[cfg(not(target_arch = "wasm32"))]
"bc5d.info" => run_bc5d_info(&request.request),
other => error(
BridgeErrorCode::UnknownCommand,
format!(
"unknown command '{other}'; this build supports: {}",
command_names().join(", ")
),
None,
),
}
}
fn run_solve(inner: &Value) -> String {
if inner.is_null() {
return error(
BridgeErrorCode::InvalidRequest,
"'solve' requires a request payload (solve-json v1 document)",
None,
);
}
let inner_text = match serde_json::to_string(inner) {
Ok(text) => text,
Err(err) => {
return error(
BridgeErrorCode::InternalError,
format!("failed to re-serialize solve request: {err}"),
None,
)
}
};
let request = match crate::solve_json::decode_solve_request_v1(&inner_text) {
Ok(request) => request,
Err(envelope) => return command_error("solve request rejected", &envelope),
};
match crate::solve_v1(request) {
Ok(successful) => match serde_json::to_value(&successful) {
Ok(result) => success("solve", result),
Err(err) => error(
BridgeErrorCode::InternalError,
format!("failed to serialize solve result: {err}"),
None,
),
},
Err(envelope) => command_error("solve failed", &envelope),
}
}
fn run_service<Req, Resp, E, F>(inner: &Value, command: &'static str, service: F) -> String
where
Req: serde::de::DeserializeOwned,
Resp: serde::Serialize,
E: std::fmt::Display,
F: FnOnce(&Req) -> Result<Resp, E>,
{
if inner.is_null() {
return error(
BridgeErrorCode::InvalidRequest,
format!("'{command}' requires a request payload"),
None,
);
}
let request: Req = match serde_json::from_value(inner.clone()) {
Ok(request) => request,
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("{command} request rejected: {err}"),
None,
)
}
};
match service(&request) {
Ok(response) => match serde_json::to_value(&response) {
Ok(result) => success(command, result),
Err(err) => error(
BridgeErrorCode::InternalError,
format!("failed to serialize {command} result: {err}"),
None,
),
},
Err(err) => error(
BridgeErrorCode::CommandFailed,
format!("{command} failed: {err}"),
None,
),
}
}
fn run_service_detailed<Req, Resp, E, F, D>(
inner: &Value,
command: &'static str,
service: F,
details: D,
) -> String
where
Req: serde::de::DeserializeOwned,
Resp: serde::Serialize,
E: std::fmt::Display,
F: FnOnce(&Req) -> Result<Resp, E>,
D: FnOnce(&E) -> Option<Value>,
{
if inner.is_null() {
return error(
BridgeErrorCode::InvalidRequest,
format!("'{command}' requires a request payload"),
None,
);
}
let request: Req = match serde_json::from_value(inner.clone()) {
Ok(request) => request,
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("{command} request rejected: {err}"),
None,
)
}
};
match service(&request) {
Ok(response) => match serde_json::to_value(&response) {
Ok(result) => success(command, result),
Err(err) => error(
BridgeErrorCode::InternalError,
format!("failed to serialize {command} result: {err}"),
None,
),
},
Err(err) => {
let d = details(&err);
error(
BridgeErrorCode::CommandFailed,
format!("{command} failed: {err}"),
d,
)
}
}
}
#[cfg(feature = "pdf")]
pub const MAX_PDF_BYTES: usize = 4 * 1024 * 1024;
#[cfg(feature = "pdf")]
fn pdf_over_cap_error(byte_length: usize, row_count: usize, page_count: usize) -> Option<String> {
(byte_length > MAX_PDF_BYTES).then(|| {
error(
BridgeErrorCode::ResourceLimit,
format!(
"generated dope card is {byte_length} bytes; the limit is {MAX_PDF_BYTES} \
({row_count} rows, {page_count} pages)"
),
None,
)
})
}
#[cfg(feature = "pdf")]
const STORED_CARD_KEY: &str = "stored_card";
#[cfg(feature = "pdf")]
fn run_card_pdf(inner: &Value) -> String {
use crate::card_service::CardServiceError;
if inner.is_null() {
return error(
BridgeErrorCode::InvalidRequest,
"'card.pdf' requires a request payload (card v1 document)",
None,
);
}
let mut payload = inner.clone();
let stored_value = payload
.as_object_mut()
.and_then(|object| object.remove(STORED_CARD_KEY))
.filter(|value| !value.is_null());
let request: crate::card_service::CardRequestV1 = match serde_json::from_value(payload) {
Ok(request) => request,
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("card.pdf request rejected: {err}"),
None,
)
}
};
let stored: Option<crate::card_service::StoredCardV1> = match stored_value {
Some(value) => match serde_json::from_value(value) {
Ok(stored) => Some(stored),
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("card.pdf {STORED_CARD_KEY} rejected: {err}"),
None,
)
}
},
None => None,
};
let card = match crate::card_service::pdf_card_v1(&request, stored.as_ref()) {
Ok(card) => card,
Err(err @ CardServiceError::TooLarge(_)) => {
return error(
BridgeErrorCode::ResourceLimit,
format!("card.pdf refused: {err}"),
None,
)
}
Err(err) => {
return error(
BridgeErrorCode::CommandFailed,
format!("card.pdf failed: {err}"),
None,
)
}
};
let byte_length = card.pdf_bytes.len();
if let Some(envelope) = pdf_over_cap_error(byte_length, card.row_count, card.page_count) {
return envelope;
}
success(
"card.pdf",
json!({
"pdf_base64": encode_base64(&card.pdf_bytes),
"byte_length": byte_length,
"page_count": card.page_count,
"row_count": card.row_count,
"kind": crate::card_service::PDF_CARD_KIND,
"source": card.source.as_str(),
"unprintable_title_chars": card.unprintable_title_chars,
}),
)
}
#[cfg(feature = "pdf")]
fn encode_base64(bytes: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let triple = (u32::from(chunk[0]) << 16)
| (u32::from(chunk.get(1).copied().unwrap_or(0)) << 8)
| u32::from(chunk.get(2).copied().unwrap_or(0));
out.push(char::from(ALPHABET[(triple >> 18) as usize & 63]));
out.push(char::from(ALPHABET[(triple >> 12) as usize & 63]));
out.push(if chunk.len() > 1 {
char::from(ALPHABET[(triple >> 6) as usize & 63])
} else {
'='
});
out.push(if chunk.len() > 2 {
char::from(ALPHABET[triple as usize & 63])
} else {
'='
});
}
out
}
fn command_error<E: Serialize>(message: &str, typed: &E) -> String {
let details = serde_json::to_value(typed).ok();
error(BridgeErrorCode::CommandFailed, message, details)
}
fn decode_profile_document(
inner: &Value,
command: &'static str,
) -> Result<crate::profile::ProfileData, String> {
if inner.is_null() {
return Err(error(
BridgeErrorCode::InvalidRequest,
format!("'{command}' requires a request payload (a ProfileData JSON document)"),
None,
));
}
serde_json::from_value(inner.clone()).map_err(|err| {
error(
BridgeErrorCode::InvalidRequest,
format!("{command} request is not a ProfileData document: {err}"),
None,
)
})
}
fn run_profile_validate(inner: &Value) -> String {
let profile = match decode_profile_document(inner, "profile.validate") {
Ok(profile) => profile,
Err(envelope) => return envelope,
};
let warnings = profile.validation_warnings();
match serde_json::to_value(&profile) {
Ok(normalized) => success(
"profile.validate",
json!({
"valid": warnings.is_empty(),
"warnings": warnings,
"normalized": normalized,
}),
),
Err(err) => error(
BridgeErrorCode::InternalError,
format!("failed to serialize normalized profile: {err}"),
None,
),
}
}
fn run_profile_normalize(inner: &Value) -> String {
let profile = match decode_profile_document(inner, "profile.normalize") {
Ok(profile) => profile,
Err(envelope) => return envelope,
};
match serde_json::to_value(&profile) {
Ok(normalized) => success("profile.normalize", json!({ "profile": normalized })),
Err(err) => error(
BridgeErrorCode::InternalError,
format!("failed to serialize normalized profile: {err}"),
None,
),
}
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Bc5dInfoRequest {
path: String,
}
#[cfg(not(target_arch = "wasm32"))]
fn run_bc5d_info(inner: &Value) -> String {
if inner.is_null() {
return error(
BridgeErrorCode::InvalidRequest,
"'bc5d.info' requires a request payload ({\"path\": ...})",
None,
);
}
let request: Bc5dInfoRequest = match serde_json::from_value(inner.clone()) {
Ok(request) => request,
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("bc5d.info request rejected: {err}"),
None,
)
}
};
let table = match crate::bc_table_5d::path_cache::load_verified(std::path::Path::new(
&request.path,
)) {
Ok(table) => table,
Err(err) => {
return error(
BridgeErrorCode::CommandFailed,
format!("bc5d.info: not a usable BC5D table: {err}"),
None,
)
}
};
let (weight, bc, muzzle_vel, current_vel, drag_types) = table.bin_counts();
let (weight_lo, weight_hi) = table.weight_range();
let (vel_lo, vel_hi) = table.velocity_range();
success(
"bc5d.info",
json!({
"valid": true,
"crc_ok": true,
"format_version": table.version(),
"caliber": table.caliber(),
"caliber_key": table.caliber_key(),
"api_version": table.api_version(),
"generated_timestamp": table.timestamp(),
"bins": {
"weight": weight,
"bc": bc,
"muzzle_velocity": muzzle_vel,
"current_velocity": current_vel,
"drag_types": drag_types,
},
"total_cells": table.total_cells(),
"weight_range_grains": [weight_lo, weight_hi],
"velocity_range_fps": [vel_lo, vel_hi],
}),
)
}
#[cfg(feature = "profile-import")]
pub const MAX_A7P_DECODED_BYTES: usize = 1024 * 1024;
#[cfg(feature = "profile-import")]
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ProfileImportA7pRequest {
a7p_base64: String,
#[serde(default)]
zero_click: Option<String>,
#[serde(default)]
strict: bool,
}
#[cfg(feature = "profile-import")]
fn run_profile_import_a7p(inner: &Value) -> String {
use crate::profile_import::{map_a7p_to_profile, parse_a7p, EnvelopeStatus};
if inner.is_null() {
return error(
BridgeErrorCode::InvalidRequest,
"'profile.import_a7p' requires a request payload ({\"a7p_base64\": ...})",
None,
);
}
let request: ProfileImportA7pRequest = match serde_json::from_value(inner.clone()) {
Ok(request) => request,
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("profile.import_a7p request rejected: {err}"),
None,
)
}
};
let zero_click = match request.zero_click.as_deref() {
Some(raw) => match crate::adjustment::parse_click_value(raw) {
Ok(click) => Some(click),
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("profile.import_a7p zero_click: {err}"),
None,
)
}
},
None => None,
};
let bytes = match decode_base64(&request.a7p_base64) {
Ok(bytes) => bytes,
Err(err) => {
return error(
BridgeErrorCode::InvalidRequest,
format!("profile.import_a7p a7p_base64: {err}"),
None,
)
}
};
if bytes.len() > MAX_A7P_DECODED_BYTES {
return error(
BridgeErrorCode::ResourceLimit,
format!(
"decoded .a7p payload is {} bytes; the limit is {MAX_A7P_DECODED_BYTES}",
bytes.len()
),
None,
);
}
let doc = match parse_a7p(&bytes) {
Ok(doc) => doc,
Err(err) => {
return error(
BridgeErrorCode::CommandFailed,
format!("not a usable .a7p file: {err}"),
None,
)
}
};
if request.strict {
if let EnvelopeStatus::Mismatch { expected, actual } = &doc.envelope {
return error(
BridgeErrorCode::CommandFailed,
format!(
"checksum mismatch (file says {expected}, payload hashes to {actual}) — refusing under strict"
),
None,
);
}
}
let outcome = match map_a7p_to_profile(&doc, None, zero_click) {
Ok(outcome) => outcome,
Err(err) => return error(BridgeErrorCode::CommandFailed, err, None),
};
let unknown_fields: Vec<Value> = doc
.unknown_fields
.iter()
.map(|u| json!({ "context": u.context, "number": u.number }))
.collect();
match serde_json::to_value(&outcome.profile) {
Ok(profile) => success(
"profile.import_a7p",
json!({
"profile": profile,
"warnings": outcome.report.warnings,
"mapped": outcome.report.mapped,
"unmapped": outcome.report.unmapped,
"unknown_fields": unknown_fields,
}),
),
Err(err) => error(
BridgeErrorCode::InternalError,
format!("failed to serialize imported profile: {err}"),
None,
),
}
}
#[cfg(feature = "profile-import")]
fn decode_base64(input: &str) -> Result<Vec<u8>, String> {
fn sextet(c: u8) -> Result<u32, String> {
match c {
b'A'..=b'Z' => Ok(u32::from(c - b'A')),
b'a'..=b'z' => Ok(u32::from(c - b'a') + 26),
b'0'..=b'9' => Ok(u32::from(c - b'0') + 52),
b'+' => Ok(62),
b'/' => Ok(63),
_ => Err(format!("invalid base64 character {:?}", char::from(c))),
}
}
let bytes = input.as_bytes();
let data = match bytes {
[rest @ .., b'=', b'='] => rest,
[rest @ .., b'='] => rest,
_ => bytes,
};
if data.contains(&b'=') {
return Err("'=' is only valid as trailing padding".to_string());
}
if data.len() % 4 == 1 {
return Err("base64 text has an impossible length (4n+1 data characters)".to_string());
}
let mut out = Vec::with_capacity(data.len() / 4 * 3 + 2);
let mut acc: u32 = 0;
let mut bits: u32 = 0;
for &c in data {
acc = (acc << 6) | sextet(c)?;
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((acc >> bits) as u8);
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn call(value: Value) -> Value {
let raw = bridge_call(&value.to_string());
serde_json::from_str(&raw).expect("bridge output must be valid JSON")
}
#[test]
fn capabilities_reports_commands_and_versions() {
let out = call(json!({"api_version": 1, "command": "meta.capabilities"}));
assert_eq!(out["ok"], true);
assert_eq!(out["api_version"], 1);
assert_eq!(out["result"]["engine_version"], ENGINE_VERSION);
let commands: Vec<String> =
serde_json::from_value(out["result"]["commands"].clone()).unwrap();
assert!(commands.contains(&"solve".to_string()));
assert!(commands.contains(&"meta.capabilities".to_string()));
}
#[test]
fn invalid_json_is_an_envelope_not_a_panic() {
let out: Value = serde_json::from_str(&bridge_call("{not json")).unwrap();
assert_eq!(out["ok"], false);
assert_eq!(out["error"]["code"], "invalid_json");
}
#[test]
fn unknown_envelope_field_is_rejected() {
let out = call(json!({"api_version": 1, "command": "meta.version", "extra": 1}));
assert_eq!(out["ok"], false);
assert_eq!(out["error"]["code"], "invalid_json");
}
#[test]
fn unknown_command_lists_supported_ones() {
let out = call(json!({"api_version": 1, "command": "card.semaphore"}));
assert_eq!(out["error"]["code"], "unknown_command");
assert!(out["error"]["message"]
.as_str()
.unwrap()
.contains("meta.capabilities"));
}
#[test]
fn wrong_api_version_is_rejected() {
let out = call(json!({"api_version": 99, "command": "meta.version"}));
assert_eq!(out["error"]["code"], "unsupported_api_version");
}
#[test]
fn oversize_request_is_a_resource_limit() {
let big = format!(
r#"{{"api_version":1,"command":"meta.version","request":"{}"}}"#,
"x".repeat(MAX_REQUEST_BYTES)
);
let out: Value = serde_json::from_str(&bridge_call(&big)).unwrap();
assert_eq!(out["error"]["code"], "resource_limit");
}
#[test]
fn solve_without_payload_is_invalid_request() {
let out = call(json!({"api_version": 1, "command": "solve"}));
assert_eq!(out["error"]["code"], "invalid_request");
}
#[test]
fn profile_commands_without_payload_are_invalid_requests() {
for command in ["profile.validate", "profile.normalize"] {
let out = call(json!({"api_version": 1, "command": command}));
assert_eq!(out["error"]["code"], "invalid_request", "{command}: {out}");
assert!(
out["error"]["message"]
.as_str()
.unwrap()
.contains("ProfileData"),
"{command}: {out}"
);
}
}
#[test]
fn capabilities_lists_profile_commands_and_gates_import_on_the_feature() {
let out = call(json!({"api_version": 1, "command": "meta.capabilities"}));
let commands: Vec<String> =
serde_json::from_value(out["result"]["commands"].clone()).unwrap();
assert!(commands.contains(&"profile.validate".to_string()));
assert!(commands.contains(&"profile.normalize".to_string()));
assert_eq!(
commands.contains(&"profile.import_a7p".to_string()),
cfg!(feature = "profile-import"),
"profile.import_a7p must be listed exactly when compiled in"
);
assert_eq!(
commands.contains(&"bc5d.info".to_string()),
cfg!(not(target_arch = "wasm32")),
"bc5d.info must be listed exactly when the build has filesystem access"
);
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn bc5d_info_without_payload_or_with_missing_file_fails_cleanly() {
let out = call(json!({"api_version": 1, "command": "bc5d.info"}));
assert_eq!(out["error"]["code"], "invalid_request", "{out}");
let out = call(json!({
"api_version": 1,
"command": "bc5d.info",
"request": {"path": "/nonexistent/bc5d_308.bin"}
}));
assert_eq!(out["error"]["code"], "command_failed", "{out}");
assert!(
out["error"]["message"]
.as_str()
.unwrap()
.contains("not a usable BC5D table"),
"{out}"
);
}
#[cfg(feature = "profile-import")]
#[test]
fn base64_decoder_round_trips_and_rejects_garbage() {
for (text, bytes) in [
("", &b""[..]),
("Zg==", b"f"),
("Zm8=", b"fo"),
("Zm9v", b"foo"),
("Zm9vYg==", b"foob"),
("Zm9vYmE=", b"fooba"),
("Zm9vYmFy", b"foobar"),
] {
assert_eq!(decode_base64(text).unwrap(), bytes, "{text}");
}
assert!(decode_base64("Zm9v\n").is_err(), "whitespace is rejected");
assert!(decode_base64("Zg=X").is_err(), "inner padding is rejected");
assert!(decode_base64("Z").is_err(), "4n+1 length is rejected");
assert!(decode_base64("Zm9v!").is_err(), "non-alphabet byte is rejected");
}
#[test]
fn capabilities_gates_card_pdf_on_the_pdf_feature() {
let out = call(json!({"api_version": 1, "command": "meta.capabilities"}));
let commands: Vec<String> =
serde_json::from_value(out["result"]["commands"].clone()).unwrap();
assert_eq!(
commands.contains(&"card.pdf".to_string()),
cfg!(feature = "pdf"),
"card.pdf must be listed exactly when compiled in: {out}"
);
let features: Vec<String> =
serde_json::from_value(out["result"]["features"].clone()).unwrap();
assert_eq!(
features.contains(&"pdf".to_string()),
cfg!(feature = "pdf"),
"the command list and the feature list must agree: {out}"
);
}
#[cfg(not(feature = "pdf"))]
#[test]
fn card_pdf_is_an_unknown_command_without_the_pdf_feature() {
let out = call(json!({
"api_version": 1,
"command": "card.pdf",
"request": {
"muzzle_velocity": 2600.0, "ballistic_coefficient": 0.243,
"mass": 175.0, "diameter": 0.308,
"zero_distance": 100.0, "start": 100.0, "end": 300.0, "step": 100.0
}
}));
assert_eq!(out["error"]["code"], "unknown_command", "{out}");
}
#[test]
fn the_pdf_presentation_block_is_accepted_by_the_on_screen_card_in_every_build() {
let out = call(json!({
"api_version": 1,
"command": "card.range_table",
"request": {
"muzzle_velocity": 2600.0, "ballistic_coefficient": 0.243,
"mass": 175.0, "diameter": 0.308,
"zero_distance": 100.0, "start": 100.0, "end": 300.0, "step": 100.0,
"pdf": {"title": "Stored Card", "target_speed": 8.0, "font_preset": "large"}
}
}));
assert_eq!(out["ok"], true, "{out}");
assert_eq!(out["result"]["kind"], "range_table", "{out}");
}
#[cfg(feature = "pdf")]
#[test]
fn card_pdf_without_payload_is_invalid_request() {
let out = call(json!({"api_version": 1, "command": "card.pdf"}));
assert_eq!(out["error"]["code"], "invalid_request", "{out}");
assert!(
out["error"]["message"].as_str().unwrap().contains("card v1 document"),
"{out}"
);
}
#[cfg(feature = "pdf")]
#[test]
fn pdf_output_cap_refuses_only_over_the_limit() {
assert!(pdf_over_cap_error(0, 0, 0).is_none());
assert!(
pdf_over_cap_error(MAX_PDF_BYTES, 6, 1).is_none(),
"a document exactly at the cap fits"
);
let envelope: Value =
serde_json::from_str(&pdf_over_cap_error(MAX_PDF_BYTES + 1, 6, 1).expect("over cap"))
.unwrap();
assert_eq!(envelope["ok"], false);
assert_eq!(envelope["error"]["code"], "resource_limit");
let message = envelope["error"]["message"].as_str().unwrap();
assert!(message.contains("dope card"), "{envelope}");
assert!(message.contains("6 rows"), "{envelope}");
assert!(message.contains("1 pages"), "{envelope}");
for absent in ["coarsen", "shorten"] {
assert!(!message.contains(absent), "{envelope}");
}
}
#[cfg(feature = "pdf")]
#[test]
fn base64_encoder_matches_the_rfc_4648_vectors() {
for (bytes, text) in [
(&b""[..], ""),
(b"f", "Zg=="),
(b"fo", "Zm8="),
(b"foo", "Zm9v"),
(b"foob", "Zm9vYg=="),
(b"fooba", "Zm9vYmE="),
(b"foobar", "Zm9vYmFy"),
] {
assert_eq!(encode_base64(bytes), text, "{bytes:?}");
}
assert_eq!(encode_base64(&[0xff, 0xff, 0xff]), "////");
assert_eq!(encode_base64(&[0x00, 0x00, 0x00]), "AAAA");
assert_eq!(encode_base64(&[0xfb, 0xff, 0xbf]), "+/+/");
}
#[cfg(all(feature = "pdf", feature = "profile-import"))]
#[test]
fn base64_encode_decode_round_trips_arbitrary_bytes() {
for len in 0..=32usize {
let bytes: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(37).wrapping_add(11)).collect();
let decoded = decode_base64(&encode_base64(&bytes)).expect("own output decodes");
assert_eq!(decoded, bytes, "len {len}");
}
}
#[test]
fn solve_with_bad_schema_carries_typed_details() {
let out = call(json!({
"api_version": 1,
"command": "solve",
"request": {"schema_version": 1, "unknown_field": true}
}));
assert_eq!(out["error"]["code"], "command_failed");
assert_eq!(out["error"]["details"]["status"], "error");
}
}