Skip to content

Commit 775f49d

Browse files
committed
Add utility to add url paths to the spec
1 parent a91c810 commit 775f49d

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed

compiler-rs/Cargo.lock

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

compiler-rs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ derive_more = "1.0.0-beta.6"
1717
either_n = "0.2"
1818
icu_segmenter = "1"
1919
indexmap = "2"
20+
itertools = "0.14"
2021
maplit = "1"
2122
once_cell = "1.16"
2223
openapiv3 = "2"

compiler-rs/clients_schema/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ serde_json = { workspace = true }
1111
once_cell = { workspace = true }
1212
anyhow = { workspace = true }
1313
indexmap = { workspace = true, features = ["serde"] }
14+
itertools = { workspace = true }
15+
1416

1517
arcstr = { workspace = true, features = ["serde", "substr"] }
1618
clap = { workspace = true, features = ["derive"] }
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use std::collections::HashMap;
2+
use std::path::{Path, PathBuf};
3+
use clap::Parser;
4+
use itertools::Itertools;
5+
6+
7+
fn main() -> anyhow::Result<()> {
8+
let cli = Cli::parse();
9+
cli.run()?;
10+
Ok(())
11+
}
12+
13+
// Example usage:
14+
// (cd compiler-rs; find ../specification -name '*Request.ts' | cargo run --bin add_url_paths ../output/schema/schema.json | sh)
15+
16+
/// Adds url paths to request definitions. Stdin must be a list of files, one per line.
17+
/// Outputs a shell script that uses ast-grep.
18+
#[derive(Debug, Parser)]
19+
#[command(author, version, about, long_about)]
20+
pub struct Cli {
21+
/// input schema file, eg: ../output/schema/schema-no-generics.json
22+
schema: PathBuf,
23+
}
24+
25+
impl Cli {
26+
pub fn run(&self) -> anyhow::Result<()> {
27+
28+
// Canonicalize all file names, so that we can do some suffix mapping from the schema locations.
29+
let files: Vec<PathBuf> = std::io::read_to_string(std::io::stdin())?
30+
.lines()
31+
.flat_map(|line| std::fs::canonicalize(line)
32+
.map_err(|e| {
33+
eprintln!("File {} not found", line);
34+
Result::<PathBuf, _>::Err(e)
35+
})) // Remove errors
36+
.collect();
37+
38+
let json = std::fs::read_to_string(&self.schema)?;
39+
let schema = clients_schema::IndexedModel::from_reader(json.as_bytes())?;
40+
41+
let mut location_to_request = HashMap::<&Path, &clients_schema::Endpoint>::new();
42+
for ep in &schema.endpoints {
43+
let Some(req_name) = ep.request.as_ref() else {
44+
//eprintln!("Skipping endpoint {} with no request", ep.name);
45+
continue;
46+
};
47+
48+
let type_def = schema.types.get(req_name).unwrap();
49+
let location = type_def.base().spec_location.as_ref().unwrap();
50+
let location = Path::new(location.split_once('#').unwrap().0);
51+
52+
location_to_request.insert(location, ep);
53+
};
54+
55+
for file in files {
56+
if let Some((_, endpoint)) = location_to_request.iter().find(|(location, _)| file.ends_with(location)) {
57+
generate_astgrep_command(&file, endpoint);
58+
} else {
59+
eprintln!("No request found for {:?}", file);
60+
}
61+
}
62+
63+
Ok(())
64+
}
65+
}
66+
67+
fn generate_astgrep_command(file: &Path, endpoint: &clients_schema::Endpoint) {
68+
69+
let text = std::fs::read_to_string(file).unwrap();
70+
if text.contains("urls:") {
71+
eprintln!("Found an existing 'url' property. Skipping {file:?}");
72+
return;
73+
}
74+
75+
// We cannot express conditional parts in the source form of patterns.
76+
77+
// Requests with generic parameters
78+
let request_expr = if text.contains("Request<") {
79+
"Request<$$$PARAM>"
80+
} else {
81+
"Request"
82+
};
83+
84+
// A handful of requests don't have an extends clause
85+
let extends_expr = if text.contains(" extends ") {
86+
"extends $REQBASE"
87+
} else {
88+
""
89+
};
90+
91+
let urls: String = endpoint.urls.iter().map(|url| {
92+
let path = &url.path;
93+
let methods = url.methods.iter().map(|method| format!("\"{}\"", method)).join(", ");
94+
let deprecation = match &url.deprecation {
95+
Some(deprecation) => format!("/** @deprecated {} {} */\n ", deprecation.version, deprecation.description),
96+
None => "".to_string(),
97+
};
98+
99+
format!(r#" {{
100+
{deprecation}path: "{path}",
101+
methods: [{methods}]
102+
}}"#)
103+
}).join(",\n");
104+
105+
let pattern = format!(r#"interface {request_expr} {extends_expr} {{
106+
$$$PROPS
107+
}}"#);
108+
109+
let fix = format!(r#"interface {request_expr} {extends_expr} {{
110+
urls: [
111+
{urls}
112+
],
113+
$$$PROPS
114+
}}"#);
115+
116+
let file = file.to_str().unwrap();
117+
println!("#----- {file}");
118+
println!(r#"ast-grep --update-all --lang ts --pattern '{pattern}' --rewrite '{fix}' "{file}""#);
119+
120+
println!();
121+
}

0 commit comments

Comments
 (0)