u-numflow 0.14.0

Foundational mathematical primitives: statistics, probability distributions, random sampling, and collections.
Documentation
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//! The keys and names each C entry point reads, measured from the entry
//! points themselves and held in `request_keys.json` beside this file -- the
//! file a binding's contract tests check the requests they write against.
//!
//! Every request type refuses a key it does not know and names the keys it
//! does (`unknown field ..., expected one of ...`); every option refuses a name
//! it does not know and lists the names it does (`unknown_option` with
//! `expected`). So a valid request is sent with a key no request has at each
//! level, and with a name no option has at each value, and the refusals are
//! read. Each value is described as one of
//!
//! - `{"properties": {key: value, ...}}` -- an object;
//! - `{"tag": key, "variants": {name: {"properties": ...}, ...}}` -- an object
//!   whose `tag` key names which keys it has;
//! - `{"items": value}` -- an array of objects or of names;
//! - `{"enum": [name, ...]}` -- one of these names, with `"number": true` when
//!   a number is taken there too;
//! - `{"enum": [name, ...], "items": {"enum": [...]}}` -- one of these names,
//!   or an array of the other names (a named set, or the members spelt out);
//! - `{}` -- anything else: a number, an array of numbers.
//!
//! An object that does not refuse an unknown key cannot be measured, and fails
//! the measurement: a misspelt key it ignores would never reach the caller.
//!
//! `UPDATE_REQUEST_KEYS=1 cargo test --features ffi --lib request_keys`
//! rewrites the file; review its diff like any other change to the contract.

use super::*;
// What differs from one crate's copy of this module to another's.
use super::unumflow_free_string as free_string;
const FREE_STRING: &str = "unumflow_free_string";

use serde_json::{json, Value};
use std::collections::BTreeMap;
use std::ffi::CString;
use std::path::PathBuf;

type Entry = unsafe extern "C" fn(*const libc::c_char, *mut *mut libc::c_char) -> i32;

/// A key and a name no request has.
const PROBE: &str = "__probe__";

#[derive(Clone)]
enum Step {
    Key(String),
    Index(usize),
}

fn call(entry: Entry, request: &Value) -> (i32, Value) {
    let request = CString::new(request.to_string()).expect("no interior NUL");
    let mut out: *mut libc::c_char = std::ptr::null_mut();
    let code = unsafe { entry(request.as_ptr(), &mut out) };
    assert!(!out.is_null(), "every status comes with a JSON body");
    let body = unsafe { CStr::from_ptr(out) }
        .to_str()
        .expect("body is UTF-8")
        .to_owned();
    unsafe { free_string(out) };
    (code, serde_json::from_str(&body).expect("body is JSON"))
}

/// `limits.ucl`, `customers[0].id`: how a refusal's `parameter` names a place.
fn parameter(path: &[Step]) -> String {
    let mut out = String::new();
    for step in path {
        match step {
            Step::Key(k) if out.is_empty() => out.push_str(k),
            Step::Key(k) => {
                out.push('.');
                out.push_str(k);
            }
            Step::Index(i) => out.push_str(&format!("[{i}]")),
        }
    }
    out
}

/// `base` with `value` at `path`; an object on the way is made when absent.
fn put(base: &Value, path: &[Step], value: Value) -> Value {
    let mut request = base.clone();
    let mut here = &mut request;
    for step in path {
        here = match step {
            Step::Key(k) => {
                if here.is_null() {
                    *here = json!({});
                }
                here.as_object_mut()
                    .unwrap_or_else(|| panic!("{} is not an object in {base}", parameter(path)))
                    .entry(k.clone())
                    .or_insert(Value::Null)
            }
            Step::Index(i) => here.get_mut(*i).unwrap_or_else(|| {
                panic!("the base request needs an element at {}", parameter(path))
            }),
        };
    }
    *here = value;
    request
}

fn get<'a>(base: &'a Value, path: &[Step]) -> Option<&'a Value> {
    path.iter().try_fold(base, |v, step| match step {
        Step::Key(k) => v.get(k),
        Step::Index(i) => v.get(*i),
    })
}

/// The keys an unknown-field refusal of the probe key lists.
fn known_keys(body: &Value) -> Option<Vec<String>> {
    let text = body["error"].as_str()?;
    let marker = format!("unknown field `{PROBE}`, ");
    let rest = &text[text.find(&marker)? + marker.len()..];
    let rest = rest.split(" at line ").next().unwrap_or(rest);
    if rest.starts_with("there are no fields") {
        return Some(Vec::new());
    }
    Some(
        rest.split('`')
            .skip(1)
            .step_by(2)
            .map(str::to_string)
            .collect(),
    )
}

/// The names an unknown-option refusal at `at` lists.
fn known_names(body: &Value, at: &str) -> Option<Vec<String>> {
    if body["code"] != "unknown_option" || body["parameter"] != at {
        return None;
    }
    let expected = body["expected"]
        .as_array()
        .unwrap_or_else(|| panic!("an unknown option without `expected`: {body}"));
    Some(
        expected
            .iter()
            .map(|n| n.as_str().expect("a name").to_string())
            .collect(),
    )
}

/// An object that took the probe key without a word cannot be measured.
fn refuse_silence(name: &str, at: &str, body: &Value) {
    let text = body["error"].as_str().unwrap_or_default();
    assert!(
        !text.contains("missing field"),
        "{name}: {at} took a key it does not know without refusing it \
         (or the base request lacks a key it needs): {body}"
    );
}

fn object(name: &str, entry: Entry, base: &Value, path: &[Step], keys: Vec<String>) -> Value {
    let properties: BTreeMap<String, Value> = keys
        .into_iter()
        .map(|key| {
            let mut at = path.to_vec();
            at.push(Step::Key(key.clone()));
            let value = describe(name, entry, base, &at);
            (key, value)
        })
        .collect();
    json!({ "properties": properties })
}

/// What the value at `path` of a valid `base` request is.
fn describe(name: &str, entry: Entry, base: &Value, path: &[Step]) -> Value {
    let at = parameter(path);

    // An object: the probe key is refused with the keys it has.
    let (_, body) = call(entry, &put(base, path, json!({ PROBE: 0 })));
    if let Some(keys) = known_keys(&body) {
        return object(name, entry, base, path, keys);
    }
    refuse_silence(name, &at, &body);
    // An object whose tag key names its variant: the object without the tag
    // is refused at that key, and a name no variant has with the names of
    // the variants.
    let tag = body["parameter"]
        .as_str()
        .and_then(|p| p.strip_prefix(&format!("{at}.")))
        .filter(|tag| !tag.contains(['.', '[']))
        .map(str::to_string);
    let variants = tag.as_ref().and_then(|tag| {
        let (_, body) = call(entry, &put(base, path, json!({ tag.as_str(): PROBE })));
        known_names(&body, &format!("{at}.{tag}"))
    });
    if let (Some(tag), Some(variants)) = (tag, variants) {
        let variants: BTreeMap<String, Value> = variants
            .into_iter()
            .map(|variant| {
                let (_, body) = call(
                    entry,
                    &put(base, path, json!({ tag.as_str(): variant, PROBE: 0 })),
                );
                let keys: Vec<String> = known_keys(&body)
                    .unwrap_or_else(|| panic!("{name}: {at} as {variant:?}: {body}"))
                    .into_iter()
                    .filter(|k| *k != tag)
                    .collect();
                let carried = get(base, path).and_then(|o| o.get(&tag)) == Some(&json!(variant));
                let variant_base = if carried {
                    base.clone()
                } else {
                    put(base, path, json!({ tag.as_str(): variant }))
                };
                let described = object(name, entry, &variant_base, path, keys);
                (variant, described)
            })
            .collect();
        return json!({ "tag": tag, "variants": variants });
    }

    // An array of objects.
    let (_, body) = call(entry, &put(base, path, json!([{ PROBE: 0 }])));
    if let Some(keys) = known_keys(&body) {
        let mut item = path.to_vec();
        item.push(Step::Index(0));
        return json!({ "items": object(name, entry, base, &item, keys) });
    }
    refuse_silence(name, &at, &body);

    // A name, or an array of names.
    let (_, body) = call(entry, &put(base, path, json!(PROBE)));
    if let Some(names) = known_names(&body, &at) {
        // A name, or an array of other names: the array is refused with those.
        let (_, listed) = call(entry, &put(base, path, json!([PROBE])));
        let items = known_names(&listed, &at)
            .or_else(|| known_names(&listed, &format!("{at}[0]")))
            .filter(|items| *items != names);
        if let Some(items) = items {
            return json!({ "enum": names, "items": { "enum": items } });
        }
        let (_, body) = call(entry, &put(base, path, json!(1)));
        let text = body["error"].as_str().unwrap_or_default();
        return if text.contains("invalid type") {
            json!({ "enum": names })
        } else {
            json!({ "enum": names, "number": true })
        };
    }
    let (_, body) = call(entry, &put(base, path, json!([PROBE])));
    if let Some(names) = known_names(&body, &at).or_else(|| known_names(&body, &format!("{at}[0]")))
    {
        return json!({ "items": { "enum": names } });
    }
    json!({})
}

fn measure(entry_points: &[(&str, Entry, Value)]) -> Value {
    let measured: BTreeMap<&str, Value> = entry_points
        .iter()
        .map(|(name, entry, base)| {
            let (code, body) = call(*entry, base);
            assert_eq!(code, 0, "{name}: the base request is refused: {body}");
            let (_, body) = call(*entry, &json!({ PROBE: 0 }));
            let keys = known_keys(&body)
                .unwrap_or_else(|| panic!("{name} does not refuse a key it does not know: {body}"));
            (*name, object(name, *entry, base, &[], keys))
        })
        .collect();
    json!({
        "$comment": "The keys and names each C entry point reads, measured from the entry points (src/ffi/request_keys.rs). Regenerate: UPDATE_REQUEST_KEYS=1 cargo test --features ffi --lib request_keys",
        "entry_points": measured,
    })
}

/// The JSON entry points `ffi.rs` exports, read from its source: each
/// `pub unsafe extern "C" fn` and each name an `...export!` macro is given.
fn exported() -> Vec<String> {
    let source = include_str!("../ffi.rs");
    let source = &source[..source.find("// ── Tests").expect("the tests marker")];
    let mut names = Vec::new();
    let mut in_macro_call = false;
    for line in source.lines().map(str::trim) {
        if let Some(rest) = line.strip_prefix("pub unsafe extern \"C\" fn ") {
            if let Some(name) = rest.split('(').next().filter(|n| !n.starts_with('$')) {
                names.push(name.to_string());
            }
        } else if line.ends_with("export!(") && !line.starts_with("macro_rules!") {
            in_macro_call = true;
        } else if in_macro_call && !line.starts_with("///") && !line.starts_with("#[") {
            let name = line.split([',', ' ']).next().expect("a name");
            names.push(name.to_string());
            in_macro_call = false;
        }
    }
    names.retain(|n| n != FREE_STRING);
    names.sort();
    names
}

// ── The entry points and a valid request to each ──────────────────────────

fn ep(name: &'static str, entry: Entry, base: Value) -> (&'static str, Entry, Value) {
    (name, entry, base)
}

fn entry_points() -> Vec<(&'static str, Entry, Value)> {
    let normal = json!({ "kind": "normal", "mu": 0, "sigma": 1 });
    let intervals = json!([[0, 2], [1, 4], [6, 7]]);
    let calorie = json!([{ "symbol": "cal_IT", "unit": "J", "factor": 4.1868, "offset": 0 }]);
    vec![
        ep(
            "unumflow_distribution_moments",
            unumflow_distribution_moments,
            json!({ "distribution": normal }),
        ),
        ep(
            "unumflow_distribution_cdf",
            unumflow_distribution_cdf,
            json!({ "distribution": normal, "x": 0.5 }),
        ),
        ep(
            "unumflow_distribution_quantile",
            unumflow_distribution_quantile,
            json!({ "distribution": normal, "p": 0.9 }),
        ),
        ep(
            "unumflow_distribution_sample",
            unumflow_distribution_sample,
            json!({ "distribution": normal, "n": 3, "seed": 7 }),
        ),
        ep(
            "unumflow_interval_normalize",
            unumflow_interval_normalize,
            json!({ "intervals": intervals }),
        ),
        ep(
            "unumflow_interval_measure",
            unumflow_interval_measure,
            json!({ "intervals": intervals }),
        ),
        ep(
            "unumflow_interval_contains",
            unumflow_interval_contains,
            json!({ "intervals": intervals, "t": 1.5 }),
        ),
        ep(
            "unumflow_interval_union",
            unumflow_interval_union,
            json!({ "a": intervals, "b": [[3, 5]] }),
        ),
        ep(
            "unumflow_interval_intersection",
            unumflow_interval_intersection,
            json!({ "a": intervals, "b": [[3, 5]] }),
        ),
        ep(
            "unumflow_interval_difference",
            unumflow_interval_difference,
            json!({ "a": intervals, "b": [[3, 5]] }),
        ),
        ep(
            "unumflow_unit_convert",
            unumflow_unit_convert,
            json!({ "value": 1, "from": "kcal_IT", "to": "J", "definitions": calorie }),
        ),
        ep(
            "unumflow_unit_convert_difference",
            unumflow_unit_convert_difference,
            json!({ "value": 1, "from": "kcal_IT", "to": "J", "definitions": calorie }),
        ),
        ep(
            "unumflow_unit_info",
            unumflow_unit_info,
            json!({ "unit": "kcal_IT", "definitions": calorie }),
        ),
    ]
}

fn held_file() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/ffi/request_keys.json")
}

#[test]
fn every_exported_entry_point_is_measured() {
    let mut measured: Vec<String> = entry_points()
        .iter()
        .map(|(n, _, _)| n.to_string())
        .collect();
    measured.sort();
    assert_eq!(measured, exported());
}

#[test]
fn the_held_request_keys_are_what_the_entry_points_read() {
    let text = serde_json::to_string_pretty(&measure(&entry_points())).expect("JSON") + "\n";
    let path = held_file();
    if std::env::var_os("UPDATE_REQUEST_KEYS").is_some() {
        std::fs::write(&path, &text).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
        return;
    }
    let held = std::fs::read_to_string(&path)
        .unwrap_or_else(|e| panic!("{}: {e}", path.display()))
        .replace("\r\n", "\n");
    assert!(
        held == text,
        "{} is not what the entry points read. Rerun with UPDATE_REQUEST_KEYS=1 and review the \
         diff; the bindings' contract tests read this file.\nmeasured:\n{text}",
        path.display()
    );
}