arcature 0.1.2

Arcature: an opinionated full-stack Rust web framework. One package, batteries included.
Documentation
//! Turning the artifact into files an application's frontend can compile
//! against.
//!
//! Each generator is a pure function from the artifact to a `String`. File
//! IO belongs to the CLI, which knows where `resources/js/generated/` is
//! and whether the directory is gitignored; a generator that wrote its own
//! files could not be tested without a temp directory and could not be
//! reused by the dev server, which serves the same bytes over HTTP.
//!
//! The output has **no imports and no npm dependency**. Everything the
//! generated TypeScript needs is in the generated TypeScript. That is the
//! whole design: an application adds a directory to `.gitignore`, and its
//! editor, `tsc`, and Vite understand the result with no plugin, no virtual
//! module, and no package to publish or version.
//!
//! * [`type_map`] -- the single Rust -> TypeScript / JSON Schema mapping.
//! * [`routes_ts`] -- `routes.ts`, the typed route table and `route()`.
//! * [`pages_dts`] -- `pages.d.ts`, per-page prop types.
//! * [`forms_ts`] -- `forms.ts`, field names and validation rules.
//! * [`index_ts`] -- `index.ts`, the barrel `@/generated` resolves to.
//! * [`openapi`] -- the OpenAPI 3.1 document.

pub mod forms_ts;
pub mod index_ts;
pub mod openapi;
pub mod pages_dts;
pub mod routes_ts;
pub mod type_map;

/// The banner every generated TypeScript file opens with.
///
/// Named after the command rather than the crate: the reader's next action
/// is to re-run something, not to find out which library wrote the file.
pub const GENERATED_HEADER: &str = "\
// Generated by `arc typegen` from the Arcature application graph.
// Do not edit -- this file is overwritten on the next run.
";

/// Renders a string as a TypeScript double-quoted literal.
///
/// Route names, paths, and field names all originate in Rust source, so a
/// quote or a backslash is unlikely -- but a generator that emits
/// syntactically broken TypeScript for one odd character is worse than one
/// that escapes unconditionally.
#[must_use]
pub fn ts_string(value: &str) -> String {
    let mut out = String::with_capacity(value.len() + 2);
    out.push('"');
    for ch in value.chars() {
        match ch {
            '"' => out.push_str("\\\""),
            '\\' => out.push_str("\\\\"),
            '\n' => out.push_str("\\n"),
            '\r' => out.push_str("\\r"),
            '\t' => out.push_str("\\t"),
            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
            c => out.push(c),
        }
    }
    out.push('"');
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_plain_string_is_quoted_unchanged() {
        assert_eq!(ts_string("links.show"), "\"links.show\"");
    }

    #[test]
    fn quotes_and_backslashes_are_escaped() {
        assert_eq!(ts_string("a\"b\\c"), "\"a\\\"b\\\\c\"");
    }

    #[test]
    fn a_control_character_is_escaped_as_a_unicode_sequence() {
        assert_eq!(ts_string("a\u{1}b"), "\"a\\u0001b\"");
    }
}