Skip to content

Commit f262b85

Browse files
marv7000ByteOtter
andauthored
Finalize Path Generation (#16)
* Finalize Path Generation * remove name parameter and introduce uri parameter --------- Co-authored-by: ByteOtter <christopher-hock@protonmail.com>
1 parent ab20ada commit f262b85

File tree

10 files changed

+128
-55
lines changed

10 files changed

+128
-55
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
22
/output
3+
/thanix_client

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ license = "GPL-3.0"
1212

1313
[dependencies]
1414
clap = {version = "4.4.2", features = ["derive"]}
15+
convert_case = "0.6.0"
1516
serde = { version = "1.0.195", features = ["derive"] }
1617
serde_yaml = "0.9.30"

src/bindgen.rs

Lines changed: 88 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
use convert_case::{Case, Casing};
12
use serde::Deserialize;
23
use serde_yaml::{Number, Value};
34
use std::{
45
collections::HashMap,
5-
env::{current_dir, set_current_dir},
66
fs::{self, File},
77
io::Write,
88
};
@@ -42,6 +42,8 @@ struct PathOp {
4242
operation_id: Option<String>,
4343
description: Option<String>,
4444
#[serde(default)]
45+
tags: Vec<String>,
46+
#[serde(default)]
4547
parameters: Vec<Parameter>,
4648
responses: Option<HashMap<String, Response>>,
4749
}
@@ -113,22 +115,22 @@ struct Property {
113115
/// This function panics when the `output/` directory does not exist.
114116
fn create_lib_dir(output_name: &str) -> Result<(), Box<dyn std::error::Error>> {
115117
println!("Starting repackaging into crate...");
116-
let source_files = ["paths.rs", "types.rs"];
118+
let source_files = ["paths.rs", "types.rs", "util.rs"];
117119

118-
if !fs::metadata(output_name).is_ok() {
120+
if fs::metadata(output_name).is_err() {
119121
panic!("Fatal: Output directory does not exist!");
120122
}
121123

122124
std::env::set_current_dir(output_name)?;
123125

124126
for src_file in &source_files {
125-
if !fs::metadata(src_file).is_ok() {
127+
if fs::metadata(src_file).is_err() {
126128
panic!("Source file {} does not exist!", src_file);
127129
}
128130
}
129131

130132
let src_dir = "src";
131-
fs::create_dir_all(&src_dir)?;
133+
fs::create_dir_all(src_dir)?;
132134

133135
for src_file in source_files {
134136
let dest_path = format!("{}/{}", src_dir, src_file);
@@ -163,32 +165,72 @@ fn make_comment(input: String, indent: usize) -> String {
163165
.concat();
164166
}
165167

166-
fn make_fn_name_from_path(input: String) -> String {
168+
fn make_fn_name_from_path(input: &str) -> String {
167169
input.replace("/api/", "").replace('/', "_")
168170
}
169171

170172
/// Replaces reserved keywords in an input string for use in Rust.
171-
fn fix_keywords(input: String) -> String {
173+
fn fix_keywords(input: &str) -> String {
172174
input
173175
.replace("type", "typ")
174176
.replace("struct", "structure")
175177
.replace("fn", "func")
176178
}
177179

178-
fn pathop_to_string(name: String, input: &PathOp, variant: &str) -> String {
179-
format!(
180-
"{}pub fn {}({}) -> Result<Response, Error> {{ return REQWEST_CLIENT.{}(format!(\"{}\")).execute().await; }}\n",
181-
make_comment(input.description.clone().unwrap(), 0),
182-
input.operation_id.clone().unwrap_or(make_fn_name_from_path(name.clone())),
183-
input.parameters.iter().enumerate().map(|(s, p)| format!(
180+
fn pathop_to_string(path: &str, input: &PathOp, method: &str) -> String {
181+
// Create a new struct for the query parameters.
182+
let fn_struct_params = input
183+
.parameters
184+
// Filter out only the query inputs.
185+
.iter()
186+
.filter(|x| x.input == "query")
187+
.enumerate()
188+
.map(|(s, p)| {
189+
format!(
190+
"\t{}: {}{}\n",
191+
fix_keywords(&p.name),
192+
get_inner_type(p.schema.as_ref().unwrap().clone(), false),
193+
if s < &input.parameters.len() - 1 {
194+
","
195+
} else {
196+
""
197+
}
198+
)
199+
})
200+
.collect::<String>();
201+
let fn_name = input
202+
.operation_id
203+
.clone()
204+
.unwrap_or(make_fn_name_from_path(&path));
205+
let fn_struct_name = fn_name.to_case(Case::Pascal) + "Query";
206+
let fn_struct = format!("#[derive(Debug, Serialize, Deserialize)]\npub struct {fn_struct_name} {{\n{fn_struct_params}}}");
207+
let comment = make_comment(input.description.clone().unwrap(), 0);
208+
let mut path_args = input
209+
.parameters
210+
// Filter out only the path inputs.
211+
.iter()
212+
.filter(|x| x.input == "path")
213+
.enumerate()
214+
.map(|(s, p)| {
215+
format!(
184216
"{}: {}{}",
185-
fix_keywords(p.name.clone()),
217+
fix_keywords(&p.name),
186218
get_inner_type(p.schema.as_ref().unwrap().clone(), false),
187-
if s == input.parameters.len() - 1 { "" } else { ", " }
188-
)).collect::<String>(),
189-
variant,
190-
name
191-
)
219+
if s < &input.parameters.len() - 1 {
220+
","
221+
} else {
222+
""
223+
}
224+
)
225+
})
226+
.collect::<String>();
227+
if !path_args.is_empty() {
228+
path_args = ", ".to_owned() + &path_args;
229+
}
230+
return format!(
231+
include_str!("templates/path.template"),
232+
fn_struct, comment, fn_name, fn_struct_name, path_args, method, path
233+
);
192234
}
193235

194236
fn get_inner_type(items: Value, append_vec: bool) -> String {
@@ -211,14 +253,13 @@ fn get_inner_type(items: Value, append_vec: bool) -> String {
211253
"number" => "f64".to_owned(),
212254
"string" => match items.get("format") {
213255
Some(x) => match x.as_str().unwrap() {
214-
"uri" => "Uri".to_owned(),
215-
"date-time" => "DateTime".to_owned(),
256+
"uri" => "Url".to_owned(),
216257
_ => "String".to_owned(),
217258
},
218259
None => "String".to_owned(),
219260
},
220261
"boolean" => "bool".to_owned(),
221-
"object" => "Json".to_owned(),
262+
"object" => "String".to_owned(),
222263
"array" => get_inner_type(
223264
match items.get("items") {
224265
Some(z) => z.clone(),
@@ -229,7 +270,7 @@ fn get_inner_type(items: Value, append_vec: bool) -> String {
229270
_ => panic!("unhandled type!"),
230271
},
231272
// We don't know what this is so assume a JSON object.
232-
None => "Json".to_owned(),
273+
None => "String".to_owned(),
233274
},
234275
};
235276
if append_vec {
@@ -247,16 +288,19 @@ fn if_some<F: FnOnce(&T), T>(this: Option<T>, func: F) {
247288
}
248289

249290
/// Generates the Rust bindings from a file.
250-
pub fn gen(input_path: impl AsRef<std::path::Path>, output_name: String) {
291+
pub fn gen(input_path: impl AsRef<std::path::Path>, url: String) {
251292
// Parse the schema.
252293
let input = std::fs::read_to_string(input_path).unwrap();
253294
let yaml: Schema = serde_yaml::from_str(&input).unwrap();
254295

255296
// Generate output folder.
256-
_ = std::fs::create_dir(&output_name);
297+
_ = std::fs::create_dir("thanix_client");
257298

258299
// Create and open the output file for structs.
259-
let mut types_file = File::create(output_name.clone() + "/types.rs").unwrap();
300+
let mut types_file = File::create("thanix_client/types.rs").unwrap();
301+
types_file
302+
.write_all(include_str!("templates/usings.template").as_bytes())
303+
.unwrap();
260304

261305
// For every struct.
262306
for (name, comp) in &yaml.components.schemas {
@@ -285,8 +329,7 @@ pub fn gen(input_path: impl AsRef<std::path::Path>, output_name: String) {
285329
// "string" can mean either a plain or formatted string or an enum declaration.
286330
"string" => match &prop.format {
287331
Some(x) => match x.as_str() {
288-
"uri" => "Uri".to_owned(),
289-
"date-time" => "DateTime".to_owned(),
332+
"uri" => "Url".to_owned(),
290333
_ => "String".to_owned(),
291334
},
292335
None => "String".to_owned(),
@@ -295,7 +338,7 @@ pub fn gen(input_path: impl AsRef<std::path::Path>, output_name: String) {
295338
"number" => "f64".to_owned(),
296339
"boolean" => "bool".to_owned(),
297340
"array" => get_inner_type(prop.items.as_ref().unwrap().clone(), true),
298-
"object" => "Json".to_owned(),
341+
"object" => "String".to_owned(),
299342
_ => todo!(),
300343
};
301344

@@ -326,41 +369,44 @@ pub fn gen(input_path: impl AsRef<std::path::Path>, output_name: String) {
326369
}
327370

328371
// Create and open the output file for paths.
329-
let mut paths_file = File::create(output_name.clone() + "/paths.rs").unwrap();
372+
let mut paths_file = File::create("thanix_client/paths.rs").unwrap();
373+
374+
paths_file
375+
.write_all(include_str!("templates/usings.template").as_bytes())
376+
.unwrap();
330377

331378
// For every path.
332379
for (name, path) in &yaml.paths {
333380
if_some(path.get.as_ref(), |op| {
334381
paths_file
335-
.write_all(pathop_to_string(name.clone(), op, "get").as_bytes())
382+
.write_all(pathop_to_string(name, op, "get").as_bytes())
336383
.unwrap()
337384
});
338385
if_some(path.put.as_ref(), |op| {
339386
paths_file
340-
.write_all(pathop_to_string(name.clone(), op, "put").as_bytes())
387+
.write_all(pathop_to_string(name, op, "put").as_bytes())
341388
.unwrap()
342389
});
343390
if_some(path.post.as_ref(), |op| {
344391
paths_file
345-
.write_all(pathop_to_string(name.clone(), op, "post").as_bytes())
392+
.write_all(pathop_to_string(name, op, "post").as_bytes())
346393
.unwrap()
347394
});
348395
if_some(path.patch.as_ref(), |op| {
349396
paths_file
350-
.write_all(pathop_to_string(name.clone(), op, "patch").as_bytes())
397+
.write_all(pathop_to_string(name, op, "patch").as_bytes())
351398
.unwrap()
352399
});
353400
if_some(path.delete.as_ref(), |op| {
354401
paths_file
355-
.write_all(pathop_to_string(name.clone(), op, "delete").as_bytes())
402+
.write_all(pathop_to_string(name, op, "delete").as_bytes())
356403
.unwrap()
357404
});
358405
}
359-
360-
_ = match create_lib_dir(&output_name) {
361-
Ok(()) => (),
362-
Err(e) => {
363-
panic!("{}", e);
364-
}
365-
};
406+
fs::write(
407+
"thanix_client/util.rs",
408+
format!(include_str!("templates/util.rs.template"), url).as_bytes(),
409+
)
410+
.unwrap();
411+
create_lib_dir("thanix_client").unwrap();
366412
}

src/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ struct Args {
99
/// Path to a YAML schema file.
1010
#[arg(short, long)]
1111
input_file: Option<String>,
12-
/// Name of the output package (Default 'output')
13-
#[arg(short, long)]
14-
name: Option<String>,
12+
/// URI of your netbox instance
13+
#[arg(long)]
14+
uri: String,
1515
}
1616

1717
fn main() {
@@ -35,7 +35,7 @@ fn main() {
3535
);
3636

3737
match args.input_file {
38-
Some(file) => bindgen::gen(file, args.name.unwrap_or("output".to_owned())),
38+
Some(file) => bindgen::gen(file, args.uri),
3939
None => println!("Error: You need to provide a YAML schema to generate from."),
4040
}
4141
}

src/templates/Cargo.toml.template

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ path = "src/lib.rs"
99

1010
[dependencies]
1111
serde = {{ version = "1.0.195", features = ["derive"] }}
12-
serde_yaml = "0.9.30"
13-
reqwest = {{ version = "0.11.22", features = ["blocking", "json"] }}
14-
15-
[[bin]]
16-
name = "your_bin_name"
17-
path = "src/main.rs"
12+
serde_qs = "0.12.0"
13+
chrono = "0.4.31"
14+
reqwest = {{ version = "0.11.22", features = ["blocking", "json"] }}

src/templates/lib.rs.template

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
// Your library code goes here.
22
#[macro_use]
3-
extern crate serde_derive;
43

54
extern crate serde;
6-
extern crate serde_json;
7-
extern crate url;
85
extern crate reqwest;
96

7+
pub mod util;
108
pub mod paths;
119
pub mod types;

src/templates/path.template

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{}
2+
{}
3+
pub fn {}(query: {}{}) -> Result<reqwest::blocking::Response, reqwest::Error> {{
4+
return REQWEST_CLIENT.lock().unwrap().as_ref().unwrap().{}(format!("{{}}{}?{{}}", REQWEST_BASE_URL, serde_qs::to_string(&query).unwrap())).send();
5+
}}
6+

src/templates/usings.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
use serde_qs;
2+
use reqwest::Url;
3+
use crate::util::{REQWEST_BASE_URL, REQWEST_CLIENT};
4+

src/templates/util.rs.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
use std::sync::Mutex;
2+
3+
pub static REQWEST_CLIENT: Mutex<Option<reqwest::blocking::Client>> = Mutex::new(None);
4+
pub static REQWEST_BASE_URL: &str = "{}";

0 commit comments

Comments
 (0)