oa_forge_emitter_angular/
lib.rs1use std::fmt::Write;
2
3use oa_forge_ir::*;
4
5pub fn emit(api: &ApiSpec, out: &mut String) -> Result<(), std::fmt::Error> {
7 writeln!(out, "// Generated by oa-forge. Do not edit.")?;
8 writeln!(out)?;
9 writeln!(out, "import {{ Injectable }} from '@angular/core';")?;
10 writeln!(
11 out,
12 "import {{ HttpClient, HttpParams, HttpHeaders }} from '@angular/common/http';"
13 )?;
14 writeln!(out, "import type {{ Observable }} from 'rxjs';")?;
15
16 let imports = collect_type_imports(api);
17 if !imports.is_empty() {
18 writeln!(
19 out,
20 "import type {{ {} }} from './types.gen';",
21 imports.join(", ")
22 )?;
23 }
24 writeln!(out)?;
25
26 writeln!(out, "@Injectable({{ providedIn: 'root' }})")?;
27 writeln!(out, "export class ApiService {{")?;
28 writeln!(out, " constructor(private http: HttpClient) {{}}")?;
29 writeln!(out)?;
30
31 for endpoint in &api.endpoints {
32 emit_method(endpoint, out)?;
33 writeln!(out)?;
34 }
35
36 writeln!(out, "}}")?;
37
38 Ok(())
39}
40
41fn emit_method(endpoint: &Endpoint, out: &mut String) -> Result<(), std::fmt::Error> {
42 let id = &endpoint.operation_id;
43 let method = endpoint.method.as_lower();
44
45 let has_path = endpoint.has_params(&ParamLocation::Path);
46 let has_query = endpoint.has_params(&ParamLocation::Query);
47 let has_header = endpoint.has_params(&ParamLocation::Header);
48 let has_cookie = endpoint.has_params(&ParamLocation::Cookie);
49 let has_body = endpoint.request_body.is_some();
50 let has_headers = has_header || has_cookie;
51
52 let mut params = Vec::new();
53 if has_path {
54 params.push(format!("pathParams: {id}PathParams"));
55 }
56 if has_query {
57 params.push(format!("queryParams?: {id}QueryParams"));
58 }
59 if has_header {
60 params.push(format!("headerParams?: {id}HeaderParams"));
61 }
62 if has_cookie {
63 params.push(format!("cookieParams?: {id}CookieParams"));
64 }
65 if has_body {
66 params.push(format!("body: {id}Body"));
67 }
68
69 let return_type = endpoint.return_type_ts();
70 let url = path_to_template_literal(&endpoint.path);
71
72 writeln!(
73 out,
74 " {id}({params}): Observable<{return_type}> {{",
75 params = params.join(", ")
76 )?;
77
78 if has_query {
80 writeln!(out, " let params = new HttpParams();")?;
81 writeln!(out, " if (queryParams) {{")?;
82 writeln!(
83 out,
84 " for (const [key, value] of Object.entries(queryParams)) {{"
85 )?;
86 writeln!(out, " if (Array.isArray(value)) {{")?;
87 writeln!(
88 out,
89 " for (const v of value) params = params.append(key, String(v));"
90 )?;
91 writeln!(out, " }} else if (value !== undefined) {{")?;
92 writeln!(out, " params = params.set(key, String(value));")?;
93 writeln!(out, " }}")?;
94 writeln!(out, " }}")?;
95 writeln!(out, " }}")?;
96 }
97
98 if has_headers {
100 writeln!(out, " let headers = new HttpHeaders();")?;
101 }
102 if has_header {
103 writeln!(out, " if (headerParams) {{")?;
104 writeln!(
105 out,
106 " for (const [key, value] of Object.entries(headerParams)) {{"
107 )?;
108 writeln!(
109 out,
110 " if (value !== undefined) headers = headers.set(key, String(value));"
111 )?;
112 writeln!(out, " }}")?;
113 writeln!(out, " }}")?;
114 }
115 if has_cookie {
116 writeln!(out, " if (cookieParams) {{")?;
117 writeln!(
118 out,
119 " const cookie = Object.entries(cookieParams).filter(([, v]) => v !== undefined).map(([k, v]) => `${{k}}=${{v}}`).join('; ');"
120 )?;
121 writeln!(out, " headers = headers.set('Cookie', cookie);")?;
122 writeln!(out, " }}")?;
123 }
124
125 let options = build_options(has_query, has_headers, endpoint.response_type.clone());
127
128 if has_body {
129 if options.is_empty() {
130 writeln!(
131 out,
132 " return this.http.{method}<{return_type}>(`{url}`, body);"
133 )?;
134 } else {
135 writeln!(
136 out,
137 " return this.http.{method}<{return_type}>(`{url}`, body, {options});"
138 )?;
139 }
140 } else if matches!(
141 endpoint.method,
142 HttpMethod::Post | HttpMethod::Put | HttpMethod::Patch
143 ) {
144 if options.is_empty() {
145 writeln!(
146 out,
147 " return this.http.{method}<{return_type}>(`{url}`, null);"
148 )?;
149 } else {
150 writeln!(
151 out,
152 " return this.http.{method}<{return_type}>(`{url}`, null, {options});"
153 )?;
154 }
155 } else if options.is_empty() {
156 writeln!(
157 out,
158 " return this.http.{method}<{return_type}>(`{url}`);"
159 )?;
160 } else {
161 writeln!(
162 out,
163 " return this.http.{method}<{return_type}>(`{url}`, {options});"
164 )?;
165 }
166
167 writeln!(out, " }}")?;
168
169 Ok(())
170}
171
172fn build_options(has_query: bool, has_headers: bool, response_type: ResponseType) -> String {
173 let mut parts = Vec::new();
174
175 if has_query {
176 parts.push("params".to_string());
177 }
178 if has_headers {
179 parts.push("headers".to_string());
180 }
181
182 match response_type {
183 ResponseType::Text => parts.push("responseType: 'text' as const".to_string()),
184 ResponseType::Blob => parts.push("responseType: 'blob' as const".to_string()),
185 _ => {}
186 }
187
188 if parts.is_empty() {
189 String::new()
190 } else {
191 format!("{{ {} }}", parts.join(", "))
192 }
193}