@typokit/plugin-axum 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ use super::GeneratedOutput;
2
+
3
+ /// Generate middleware stub files.
4
+ ///
5
+ /// Produces:
6
+ /// - `src/middleware/auth.rs` with an Axum middleware stub (overwrite: false)
7
+ /// - `src/middleware/mod.rs` with pub mod auth declaration (overwrite: true)
8
+ pub fn generate_middleware() -> Vec<GeneratedOutput> {
9
+ vec![
10
+ generate_auth_middleware(),
11
+ generate_middleware_mod(),
12
+ ]
13
+ }
14
+
15
+ /// Generate the `src/middleware/auth.rs` with an Axum middleware stub.
16
+ fn generate_auth_middleware() -> GeneratedOutput {
17
+ let mut s = String::new();
18
+
19
+ s.push_str("// AUTO-GENERATED by @typokit/transform-native\n");
20
+ s.push_str("// This file will NOT be overwritten — edit freely.\n\n");
21
+
22
+ s.push_str("use axum::{\n");
23
+ s.push_str(" extract::Request,\n");
24
+ s.push_str(" http::StatusCode,\n");
25
+ s.push_str(" middleware::Next,\n");
26
+ s.push_str(" response::Response,\n");
27
+ s.push_str("};\n\n");
28
+
29
+ s.push_str("/// Authentication middleware.\n");
30
+ s.push_str("///\n");
31
+ s.push_str("/// Validates the incoming request for authentication credentials.\n");
32
+ s.push_str("/// Attach to routes via `axum::middleware::from_fn(auth)`.\n");
33
+ s.push_str("pub async fn auth(\n");
34
+ s.push_str(" request: Request,\n");
35
+ s.push_str(" next: Next,\n");
36
+ s.push_str(") -> Result<Response, StatusCode> {\n");
37
+ s.push_str(" // TODO: Implement authentication logic\n");
38
+ s.push_str(" // Example: Check for a valid Bearer token in the Authorization header\n");
39
+ s.push_str(" //\n");
40
+ s.push_str(" // let auth_header = request\n");
41
+ s.push_str(" // .headers()\n");
42
+ s.push_str(" // .get(\"Authorization\")\n");
43
+ s.push_str(" // .and_then(|v| v.to_str().ok());\n");
44
+ s.push_str(" //\n");
45
+ s.push_str(" // match auth_header {\n");
46
+ s.push_str(" // Some(token) if token.starts_with(\"Bearer \") => {\n");
47
+ s.push_str(" // // Validate the token here\n");
48
+ s.push_str(" // Ok(next.run(request).await)\n");
49
+ s.push_str(" // }\n");
50
+ s.push_str(" // _ => Err(StatusCode::UNAUTHORIZED),\n");
51
+ s.push_str(" // }\n");
52
+ s.push_str("\n");
53
+ s.push_str(" Ok(next.run(request).await)\n");
54
+ s.push_str("}\n");
55
+
56
+ GeneratedOutput {
57
+ path: "src/middleware/auth.rs".to_string(),
58
+ content: s,
59
+ overwrite: false,
60
+ }
61
+ }
62
+
63
+ /// Generate the `src/middleware/mod.rs` with pub mod declarations.
64
+ fn generate_middleware_mod() -> GeneratedOutput {
65
+ let mut output = String::new();
66
+ output.push_str("// AUTO-GENERATED by @typokit/transform-native — DO NOT EDIT\n\n");
67
+ output.push_str("pub mod auth;\n");
68
+
69
+ GeneratedOutput {
70
+ path: "src/middleware/mod.rs".to_string(),
71
+ content: output,
72
+ overwrite: true,
73
+ }
74
+ }
75
+
76
+ #[cfg(test)]
77
+ mod tests {
78
+ use super::*;
79
+
80
+ #[test]
81
+ fn test_generate_middleware_produces_two_files() {
82
+ let outputs = generate_middleware();
83
+ assert_eq!(outputs.len(), 2);
84
+ }
85
+
86
+ #[test]
87
+ fn test_auth_middleware_file_path() {
88
+ let outputs = generate_middleware();
89
+ let auth = outputs.iter().find(|o| o.path == "src/middleware/auth.rs").unwrap();
90
+ assert!(!auth.overwrite);
91
+ }
92
+
93
+ #[test]
94
+ fn test_auth_middleware_uses_from_fn_pattern() {
95
+ let outputs = generate_middleware();
96
+ let auth = outputs.iter().find(|o| o.path == "src/middleware/auth.rs").unwrap();
97
+ assert!(auth.content.contains("pub async fn auth("));
98
+ assert!(auth.content.contains("request: Request"));
99
+ assert!(auth.content.contains("next: Next"));
100
+ assert!(auth.content.contains("Result<Response, StatusCode>"));
101
+ }
102
+
103
+ #[test]
104
+ fn test_auth_middleware_calls_next() {
105
+ let outputs = generate_middleware();
106
+ let auth = outputs.iter().find(|o| o.path == "src/middleware/auth.rs").unwrap();
107
+ assert!(auth.content.contains("next.run(request).await"));
108
+ }
109
+
110
+ #[test]
111
+ fn test_auth_middleware_imports() {
112
+ let outputs = generate_middleware();
113
+ let auth = outputs.iter().find(|o| o.path == "src/middleware/auth.rs").unwrap();
114
+ assert!(auth.content.contains("use axum::"));
115
+ assert!(auth.content.contains("extract::Request"));
116
+ assert!(auth.content.contains("middleware::Next"));
117
+ assert!(auth.content.contains("response::Response"));
118
+ assert!(auth.content.contains("http::StatusCode"));
119
+ }
120
+
121
+ #[test]
122
+ fn test_auth_middleware_no_overwrite() {
123
+ let outputs = generate_middleware();
124
+ let auth = outputs.iter().find(|o| o.path == "src/middleware/auth.rs").unwrap();
125
+ assert!(!auth.overwrite);
126
+ assert!(auth.content.contains("will NOT be overwritten"));
127
+ }
128
+
129
+ #[test]
130
+ fn test_middleware_mod_file() {
131
+ let outputs = generate_middleware();
132
+ let mod_file = outputs.iter().find(|o| o.path == "src/middleware/mod.rs").unwrap();
133
+ assert!(mod_file.overwrite);
134
+ assert!(mod_file.content.contains("pub mod auth;"));
135
+ }
136
+
137
+ #[test]
138
+ fn test_middleware_mod_overwrite_true() {
139
+ let outputs = generate_middleware();
140
+ let mod_file = outputs.iter().find(|o| o.path == "src/middleware/mod.rs").unwrap();
141
+ assert!(mod_file.overwrite);
142
+ }
143
+
144
+ #[test]
145
+ fn test_deterministic_output() {
146
+ let outputs1 = generate_middleware();
147
+ let outputs2 = generate_middleware();
148
+
149
+ assert_eq!(outputs1.len(), outputs2.len());
150
+ for (a, b) in outputs1.iter().zip(outputs2.iter()) {
151
+ assert_eq!(a.path, b.path);
152
+ assert_eq!(a.content, b.content);
153
+ assert_eq!(a.overwrite, b.overwrite);
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,91 @@
1
+ //! Rust code generation module for TypoKit.
2
+ //!
3
+ //! Generates a complete Axum server (structs, router, sqlx DB layer, handlers)
4
+ //! from TypeScript schemas extracted by the TypoKit build pipeline.
5
+
6
+ pub mod database;
7
+ pub mod handlers;
8
+ pub mod middleware;
9
+ pub mod project;
10
+ pub mod router;
11
+ pub mod services;
12
+ pub mod structs;
13
+
14
+ use std::collections::HashMap;
15
+ use typokit_transform_native::route_compiler::RouteEntry;
16
+ use typokit_transform_native::type_extractor::TypeMetadata;
17
+
18
+ /// A single generated output file
19
+ #[derive(Debug, Clone)]
20
+ pub struct GeneratedOutput {
21
+ /// Relative path for the generated file (e.g., ".typokit/models/user.rs")
22
+ pub path: String,
23
+ /// Generated file content
24
+ pub content: String,
25
+ /// Whether to overwrite an existing file at this path
26
+ pub overwrite: bool,
27
+ }
28
+
29
+ /// Generate all Rust code from the extracted TypeScript schema types and routes.
30
+ ///
31
+ /// Generates Rust struct files with serde derives, an Axum router file
32
+ /// with typed handler registrations, a sqlx database layer with
33
+ /// CRUD repository functions and SQL migrations, per-entity
34
+ /// Axum handler files wired to repository functions, service-layer
35
+ /// stubs, middleware stubs, and the complete project scaffold
36
+ /// (Cargo.toml, main.rs, lib.rs, app.rs, error.rs).
37
+ pub fn generate(
38
+ type_map: &HashMap<String, TypeMetadata>,
39
+ routes: &[RouteEntry],
40
+ ) -> Vec<GeneratedOutput> {
41
+ let mut outputs = structs::generate_structs(type_map);
42
+ outputs.extend(router::generate_router(routes));
43
+ outputs.extend(database::generate_database(type_map));
44
+ outputs.extend(handlers::generate_handlers(type_map, routes));
45
+ outputs.extend(services::generate_services(type_map));
46
+ outputs.extend(middleware::generate_middleware());
47
+ outputs.extend(project::generate_project());
48
+ outputs
49
+ }
50
+
51
+ #[cfg(test)]
52
+ mod tests {
53
+ use super::*;
54
+ use typokit_transform_native::type_extractor::PropertyMetadata;
55
+
56
+ #[test]
57
+ fn test_generate_returns_outputs() {
58
+ let mut type_map = HashMap::new();
59
+ let mut properties = HashMap::new();
60
+ properties.insert(
61
+ "id".to_string(),
62
+ PropertyMetadata {
63
+ type_str: "string".to_string(),
64
+ optional: false,
65
+ jsdoc: None,
66
+ },
67
+ );
68
+ type_map.insert(
69
+ "User".to_string(),
70
+ TypeMetadata {
71
+ name: "User".to_string(),
72
+ properties,
73
+ jsdoc: None,
74
+ },
75
+ );
76
+
77
+ let routes = vec![];
78
+ let outputs = generate(&type_map, &routes);
79
+ assert!(!outputs.is_empty());
80
+ assert!(outputs.iter().any(|o| o.path.contains("user.rs")));
81
+ assert!(outputs.iter().any(|o| o.path.ends_with("mod.rs")));
82
+ }
83
+
84
+ #[test]
85
+ fn test_generate_includes_router_output() {
86
+ let type_map = HashMap::new();
87
+ let routes = vec![];
88
+ let outputs = generate(&type_map, &routes);
89
+ assert!(outputs.iter().any(|o| o.path == ".typokit/router.rs"));
90
+ }
91
+ }