Skip to main content

arvalez_openapi/
lib.rs

1use std::{fs, time::Instant};
2use std::path::Path;
3
4use anyhow::{Context, Result};
5
6mod diagnostic;
7mod document;
8mod importer;
9mod merge;
10mod naming;
11mod parse;
12mod schema;
13mod source;
14#[cfg(test)]
15mod tests;
16
17pub use diagnostic::{
18    DiagnosticKind, OpenApiDiagnostic, OpenApiLoadResult,
19    categorize_reference, diagnostic_pointer_tail, normalize_diagnostic_feature,
20};
21
22use importer::OpenApiImporter;
23use parse::{parse_json_openapi_document, parse_yaml_openapi_document};
24
25#[derive(Debug, Clone, Copy, Default)]
26pub struct LoadOpenApiOptions {
27    pub ignore_unhandled: bool,
28    pub emit_timings: bool,
29}
30
31pub fn load_openapi_to_ir(path: impl AsRef<Path>) -> Result<arvalez_ir::CoreIr> {
32    Ok(load_openapi_to_ir_with_options(path, LoadOpenApiOptions::default())?.ir)
33}
34
35pub fn load_openapi_to_ir_with_options(
36    path: impl AsRef<Path>,
37    options: LoadOpenApiOptions,
38) -> Result<OpenApiLoadResult> {
39    let path = path.as_ref();
40    let raw = measure_openapi_phase(options.emit_timings, "openapi_read", || {
41        fs::read_to_string(path)
42            .with_context(|| format!("failed to read OpenAPI document `{}`", path.display()))
43    })?;
44
45    let loaded = measure_openapi_phase(options.emit_timings, "openapi_parse", || {
46        match path.extension().and_then(|ext| ext.to_str()) {
47            Some("yaml") | Some("yml") => parse_yaml_openapi_document(path, &raw),
48            _ => parse_json_openapi_document(path, &raw),
49        }
50    })?;
51
52    OpenApiImporter::new(loaded.document, loaded.source, options).build_ir()
53}
54
55pub(crate) fn measure_openapi_phase<T, F>(enabled: bool, label: &str, task: F) -> Result<T>
56where
57    F: FnOnce() -> Result<T>,
58{
59    if enabled {
60        eprintln!("timing: starting {label}");
61    }
62    let started = Instant::now();
63    let value = task();
64    if enabled {
65        eprintln!(
66            "timing: {:<20} {}",
67            label,
68            format_duration(started.elapsed())
69        );
70    }
71    value
72}
73
74pub(crate) fn format_duration(duration: std::time::Duration) -> String {
75    let micros = duration.as_micros();
76    if micros < 1_000 {
77        format!("{micros}us")
78    } else if micros < 1_000_000 {
79        format!("{}ms", duration.as_millis())
80    } else {
81        format!("{:.2}s", duration.as_secs_f64())
82    }
83}