Skip to content

Commit 9d11ac6

Browse files
committed
feat: add runtime module for sqlc.dev plugin execution
- Add runtime module with run() and run_with_io() helper functions - Add comprehensive test coverage for runtime functionality - Configure llvm-cov to exclude generated protobuf code - Add crate-level documentation and improve module organization - Reset version to 0.0.0 for initial development Signed-off-by: Svetlin Ralchev <iamralch@users.noreply.github.com>
1 parent 8f35804 commit 9d11ac6

4 files changed

Lines changed: 238 additions & 4 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ prost = "0.14.1"
1010

1111
[build-dependencies]
1212
prost-build = "0.14.1"
13+
14+
[package.metadata.llvm-cov]
15+
# Exclude generated protobuf code from coverage reports
16+
ignore-filename-regex = "plugin\\.rs"

src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
pub mod plugin;
1+
//! sqlc.dev gen core library.
2+
//!
3+
//! Provides:
4+
//! - `plugin`: generated proto definitions
5+
//! - `runtime`: helper functions for running sqlc.dev plugins
26
3-
pub use prost::Message;
7+
pub mod plugin;
8+
pub mod runtime;
49

510
pub mod prelude {
611
pub use crate::plugin::*;
7-
pub use crate::Message;
12+
pub use crate::runtime::*;
13+
pub use prost::Message;
814
}

src/runtime.rs

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
use crate::plugin::{GenerateRequest, GenerateResponse};
2+
use prost::Message;
3+
use std::error::Error;
4+
use std::io::{Read, Write};
5+
6+
pub fn run<TFunc>(process: TFunc) -> Result<(), Box<dyn Error>>
7+
where
8+
TFunc: FnOnce(GenerateRequest) -> Result<GenerateResponse, Box<dyn Error>>,
9+
{
10+
let stdin = std::io::stdin();
11+
let stdout = std::io::stdout();
12+
run_with_io(stdin.lock(), stdout.lock(), process)
13+
}
14+
15+
pub fn run_with_io<TReader, TWriter, TFunc>(
16+
mut reader: TReader,
17+
mut writer: TWriter,
18+
process: TFunc,
19+
) -> Result<(), Box<dyn Error>>
20+
where
21+
TReader: Read,
22+
TWriter: Write,
23+
TFunc: FnOnce(GenerateRequest) -> Result<GenerateResponse, Box<dyn Error>>,
24+
{
25+
let mut input = Vec::new();
26+
reader.read_to_end(&mut input)?;
27+
28+
let request = GenerateRequest::decode(&input[..])?;
29+
let response = process(request)?;
30+
31+
let mut output = Vec::new();
32+
response.encode(&mut output)?;
33+
34+
writer.write_all(&output)?;
35+
Ok(())
36+
}
37+
38+
#[cfg(test)]
39+
mod tests {
40+
use super::*;
41+
use crate::plugin::{File, GenerateRequest, GenerateResponse};
42+
43+
fn create_sample_request() -> GenerateRequest {
44+
GenerateRequest {
45+
settings: None,
46+
catalog: None,
47+
queries: vec![],
48+
sqlc_version: "test".to_string(),
49+
plugin_options: vec![],
50+
global_options: vec![],
51+
}
52+
}
53+
54+
fn create_sample_response() -> GenerateResponse {
55+
GenerateResponse {
56+
files: vec![File {
57+
name: "test.rs".to_string(),
58+
contents: b"// test content".to_vec(),
59+
}],
60+
}
61+
}
62+
63+
#[test]
64+
fn test_run_with_io_success() {
65+
let mut input = Vec::new();
66+
let mut output = Vec::new();
67+
68+
let request = create_sample_request();
69+
request.encode(&mut input).unwrap();
70+
71+
let result = run_with_io(&input[..], &mut output, |req| {
72+
assert_eq!(req.sqlc_version, "test");
73+
Ok(create_sample_response())
74+
});
75+
assert!(result.is_ok(), "run_with_io should succeed");
76+
77+
let response = GenerateResponse::decode(&output[..]).unwrap();
78+
assert_eq!(response.files.len(), 1);
79+
assert_eq!(response.files[0].name, "test.rs");
80+
assert_eq!(response.files[0].contents, b"// test content");
81+
}
82+
83+
#[test]
84+
fn test_run_with_io_processor_error() {
85+
let mut input = Vec::new();
86+
let mut output = Vec::new();
87+
88+
let request = create_sample_request();
89+
request.encode(&mut input).unwrap();
90+
91+
let result = run_with_io(&input[..], &mut output, |_req| {
92+
Err("Processing failed".into())
93+
});
94+
assert!(
95+
result.is_err(),
96+
"run_with_io should fail when processor fails"
97+
);
98+
assert_eq!(result.unwrap_err().to_string(), "Processing failed");
99+
}
100+
101+
#[test]
102+
fn test_run_with_io_invalid_input() {
103+
let input = b"invalid protobuf data";
104+
let mut output = Vec::new();
105+
106+
let result = run_with_io(&input[..], &mut output, |_req| Ok(create_sample_response()));
107+
assert!(
108+
result.is_err(),
109+
"run_with_io should fail with invalid input"
110+
);
111+
}
112+
113+
#[test]
114+
fn test_run_with_io_empty_input() {
115+
let input: &[u8] = &[];
116+
let mut output = Vec::new();
117+
118+
let result = run_with_io(input, &mut output, |_req| Ok(create_sample_response()));
119+
assert!(
120+
result.is_ok(),
121+
"run_with_io should succeed with empty input (creates default request)"
122+
);
123+
124+
let response = GenerateResponse::decode(&output[..]).unwrap();
125+
assert_eq!(response.files.len(), 1);
126+
}
127+
128+
#[test]
129+
fn test_run_with_io_empty_response() {
130+
let mut input = Vec::new();
131+
let mut output = Vec::new();
132+
133+
let request = create_sample_request();
134+
request.encode(&mut input).unwrap();
135+
136+
// Processor returns empty response
137+
let result = run_with_io(&input[..], &mut output, |_req| {
138+
Ok(GenerateResponse { files: vec![] })
139+
});
140+
assert!(
141+
result.is_ok(),
142+
"run_with_io should succeed with empty response"
143+
);
144+
145+
let response = GenerateResponse::decode(&output[..]).unwrap();
146+
assert_eq!(response.files.len(), 0);
147+
}
148+
149+
#[test]
150+
fn test_run_with_io_multiple_files() {
151+
let mut input = Vec::new();
152+
let mut output = Vec::new();
153+
154+
let request = create_sample_request();
155+
request.encode(&mut input).unwrap();
156+
157+
let result = run_with_io(&input[..], &mut output, |_req| {
158+
Ok(GenerateResponse {
159+
files: vec![
160+
File {
161+
name: "file1.rs".to_string(),
162+
contents: b"content1".to_vec(),
163+
},
164+
File {
165+
name: "file2.rs".to_string(),
166+
contents: b"content2".to_vec(),
167+
},
168+
],
169+
})
170+
});
171+
assert!(result.is_ok(), "run_with_io should succeed");
172+
173+
let response = GenerateResponse::decode(&output[..]).unwrap();
174+
assert_eq!(response.files.len(), 2);
175+
assert_eq!(response.files[0].name, "file1.rs");
176+
assert_eq!(response.files[1].name, "file2.rs");
177+
}
178+
179+
#[test]
180+
fn test_run_with_io_preserves_request_data() {
181+
let mut input = Vec::new();
182+
let mut output = Vec::new();
183+
184+
let request = GenerateRequest {
185+
settings: None,
186+
catalog: None,
187+
queries: vec![],
188+
sqlc_version: "1.2.3".to_string(),
189+
plugin_options: b"test_plugin_options".to_vec(),
190+
global_options: b"test_global_options".to_vec(),
191+
};
192+
request.encode(&mut input).unwrap();
193+
194+
let result = run_with_io(&input[..], &mut output, |req| {
195+
assert_eq!(req.sqlc_version, "1.2.3");
196+
assert_eq!(req.plugin_options, b"test_plugin_options");
197+
assert_eq!(req.global_options, b"test_global_options");
198+
Ok(create_sample_response())
199+
});
200+
assert!(result.is_ok());
201+
}
202+
203+
#[test]
204+
fn test_run_with_io_large_content() {
205+
let mut input = Vec::new();
206+
let mut output = Vec::new();
207+
208+
let request = create_sample_request();
209+
request.encode(&mut input).unwrap();
210+
211+
let result = run_with_io(&input[..], &mut output, |_req| {
212+
Ok(GenerateResponse {
213+
files: vec![File {
214+
name: "large.rs".to_string(),
215+
contents: vec![b'x'; 1024 * 1024].clone(),
216+
}],
217+
})
218+
});
219+
assert!(result.is_ok(), "run_with_io should handle large content");
220+
221+
let response = GenerateResponse::decode(&output[..]).unwrap();
222+
assert_eq!(response.files[0].contents.len(), 1024 * 1024);
223+
}
224+
}

0 commit comments

Comments
 (0)