Skip to main content

oa_forge_emitter_msw/
lib.rs

1use std::fmt::Write;
2
3use oa_forge_ir::*;
4
5/// Emit MSW v2 request handlers from the API spec.
6pub 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 {{ http, HttpResponse }} from 'msw';")?;
10    writeln!(out, "import type * as Types from './types.gen';")?;
11    writeln!(out)?;
12
13    // Emit individual handler functions for composability
14    for endpoint in &api.endpoints {
15        emit_handler_function(endpoint, out)?;
16    }
17
18    // Emit default handlers array
19    writeln!(out, "export const handlers = [")?;
20    for endpoint in &api.endpoints {
21        writeln!(out, "  {}Handler(),", endpoint.operation_id)?;
22    }
23    writeln!(out, "];")?;
24
25    Ok(())
26}
27
28/// Emit a factory function for a single endpoint handler.
29/// Factory pattern allows users to override the response.
30fn emit_handler_function(endpoint: &Endpoint, out: &mut String) -> Result<(), std::fmt::Error> {
31    let id = &endpoint.operation_id;
32    let method = endpoint.method.as_lower();
33    let msw_path = path_to_colon_params(&endpoint.path);
34
35    let has_response = endpoint.response.is_some();
36    let response_type = if has_response {
37        format!("Types.{id}Response")
38    } else {
39        "undefined".to_string()
40    };
41
42    // Factory function signature
43    writeln!(
44        out,
45        "export function {id}Handler(override?: {response_type} | (() => {response_type})) {{"
46    )?;
47
48    writeln!(out, "  return http.{method}('{msw_path}', () => {{")?;
49
50    // Generate response based on response type
51    match endpoint.response_type {
52        ResponseType::Void => {
53            writeln!(out, "    return new HttpResponse(null, {{ status: 204 }});")?;
54        }
55        ResponseType::Text => {
56            writeln!(
57                out,
58                "    const data = typeof override === 'function' ? override() : override;"
59            )?;
60            writeln!(out, "    return HttpResponse.text(data as string ?? '');")?;
61        }
62        ResponseType::Blob => {
63            writeln!(out, "    return new HttpResponse(null, {{ status: 200 }});")?;
64        }
65        ResponseType::Json => {
66            writeln!(
67                out,
68                "    const data = typeof override === 'function' ? override() : override;"
69            )?;
70            writeln!(out, "    return HttpResponse.json(data ?? {{}});")?;
71        }
72    }
73
74    writeln!(out, "  }});")?;
75    writeln!(out, "}}")?;
76    writeln!(out)?;
77
78    Ok(())
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn converts_openapi_path_to_msw() {
87        assert_eq!(path_to_colon_params("/pets"), "/pets");
88        assert_eq!(path_to_colon_params("/pets/{petId}"), "/pets/:petId");
89        assert_eq!(
90            path_to_colon_params("/users/{userId}/posts/{postId}"),
91            "/users/:userId/posts/:postId"
92        );
93    }
94}