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
use crate::generator::utils::sanitize_module_name;
use crate::templates::context::Parameter;
use serde::Serialize;
/// Context for hook generation.
#[derive(Debug, Clone, Serialize)]
pub struct HookContext {
pub hook_name: String,
pub key_name: String,
pub operation_id: String,
pub http_method: String,
pub path: String,
pub path_params: Vec<Parameter>,
pub query_params: Vec<Parameter>,
pub body_type: Option<String>,
pub response_type: String,
pub module_name: String,
pub spec_name: Option<String>,
pub api_import_path: String,
pub query_keys_import_path: String,
pub param_list: String, // Full parameter list with types: "id: string, query?: { page?: number }"
pub param_names: String, // Just parameter names for function calls: "id, query"
pub path_param_names: String, // Just path parameter names: "id"
pub schema_imports: String, // Schema import statements
pub description: String,
pub success_map_type: String, // e.g., "OrdersControllerCreateResponses"
pub error_map_type: String, // e.g., "OrdersControllerCreateErrors"
pub generic_result_type: String, // e.g., "ApiResult<OrdersControllerCreateResponses, OrdersControllerCreateErrors>"
pub import_runtime_path: String, // Path to runtime types/client
}
impl HookContext {
/// Calculate import path to API functions.
/// From: src/hooks/{module}/useX.ts
/// To: {apis_dir}/{module}/index.ts
/// Note: apis_dir doesn't include spec_name (it's in config if needed), just like schemas
pub fn calculate_api_import_path(
module_name: &str,
_spec_name: Option<&str>,
apis_dir: Option<&str>,
) -> String {
let module_depth = module_name.matches('/').count() + 1; // +1 for module directory
let hooks_depth = 1; // hooks directory
let total_depth = module_depth + hooks_depth;
if let Some(apis) = apis_dir {
// Calculate relative path from hooks/{module}/ to {apis_dir}/{module}/
// Note: hooks_dir and apis_dir don't include spec_name (it's in config if needed)
let hooks_path = format!("src/hooks/{}", module_name);
let common_prefix = HookContext::find_common_prefix(&hooks_path, apis);
let hooks_relative = hooks_path
.strip_prefix(&common_prefix)
.unwrap_or(&hooks_path)
.trim_start_matches('/');
let apis_relative = apis
.strip_prefix(&common_prefix)
.unwrap_or(apis)
.trim_start_matches('/');
let hooks_depth_from_common = if hooks_relative.is_empty() {
0
} else {
hooks_relative.matches('/').count() + 1
};
let sanitized_module = sanitize_module_name(module_name);
if apis_relative.is_empty() {
format!(
"{}{}",
"../".repeat(hooks_depth_from_common),
sanitized_module
)
} else {
format!(
"{}{}/{}",
"../".repeat(hooks_depth_from_common),
apis_relative,
sanitized_module
)
}
} else {
// Fallback: assume apis is at src/apis/{module}
let sanitized_module = sanitize_module_name(module_name);
format!("{}apis/{}", "../".repeat(total_depth), sanitized_module)
}
}
/// Find the common prefix of two paths
pub fn find_common_prefix(path1: &str, path2: &str) -> String {
let parts1: Vec<&str> = path1.split('/').collect();
let parts2: Vec<&str> = path2.split('/').collect();
let mut common = Vec::new();
let min_len = parts1.len().min(parts2.len());
for i in 0..min_len {
if parts1[i] == parts2[i] {
common.push(parts1[i]);
} else {
break;
}
}
common.join("/")
}
/// Calculate import path to query keys.
/// From: {hooks_dir}/{module}/useX.ts
/// To: {query_keys_dir}/{module}.ts
/// Note: output_dir already includes spec_name if needed (from config), just like schemas/apis
pub fn calculate_query_keys_import_path(
module_name: &str,
_spec_name: Option<&str>,
hooks_dir: Option<&str>,
query_keys_dir: Option<&str>,
) -> String {
let module_depth = module_name.matches('/').count() + 1; // +1 for module directory
let hooks_depth = 1; // hooks directory
let total_depth = module_depth + hooks_depth;
if let (Some(hooks), Some(query_keys)) = (hooks_dir, query_keys_dir) {
// Calculate relative path from hooks/{module}/ to {query_keys_dir}/{module}.ts
// Note: hooks_dir and query_keys_dir don't include spec_name (it's in config if needed)
let hooks_path = format!("{}/{}", hooks, module_name);
let common_prefix = HookContext::find_common_prefix(&hooks_path, query_keys);
let hooks_relative = hooks_path
.strip_prefix(&common_prefix)
.unwrap_or(&hooks_path)
.trim_start_matches('/');
let query_keys_relative = query_keys
.strip_prefix(&common_prefix)
.unwrap_or(query_keys)
.trim_start_matches('/');
let hooks_depth_from_common = if hooks_relative.is_empty() {
0
} else {
hooks_relative.matches('/').count() + 1
};
let sanitized_module = sanitize_module_name(module_name);
if query_keys_relative.is_empty() {
format!(
"{}{}",
"../".repeat(hooks_depth_from_common),
sanitized_module
)
} else {
format!(
"{}{}/{}",
"../".repeat(hooks_depth_from_common),
query_keys_relative,
sanitized_module
)
}
} else {
// Fallback: assume query-keys is at src/query-keys/{module}
let sanitized_module = sanitize_module_name(module_name);
format!(
"{}query-keys/{}",
"../".repeat(total_depth),
sanitized_module
)
}
}
/// Calculate import path to runtime client.
/// From: src/hooks/{module}/useX.ts
/// To: src/runtime/index.ts
/// Note: hooks_dir doesn't include spec_name (it's in config if needed), just like schemas/apis
pub fn calculate_runtime_import_path(module_name: &str, _spec_name: Option<&str>) -> String {
// Calculate depth: hooks/{module}/ -> runtime/
// Example: hooks/addresses/ -> ../../runtime
let module_depth = module_name.matches('/').count() + 1; // +1 for module directory
let hooks_depth = 1; // hooks directory
let total_depth = module_depth + hooks_depth;
format!("{}runtime", "../".repeat(total_depth))
}
}