Skip to content

Commit 858ac97

Browse files
authored
feat(gen): support proto_paths for gen graphql through proto file (#3206)
1 parent fe220be commit 858ac97

File tree

7 files changed

+195
-18
lines changed

7 files changed

+195
-18
lines changed

src/cli/generator/config.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ pub enum Source<Status = UnResolved> {
8181
is_mutation: Option<bool>,
8282
field_name: String,
8383
},
84+
#[serde(rename_all = "camelCase")]
8485
Proto {
8586
src: Location<Status>,
8687
url: String,
8788
#[serde(skip_serializing_if = "Option::is_none")]
89+
proto_paths: Option<Vec<Location<Status>>>,
90+
#[serde(skip_serializing_if = "Option::is_none")]
8891
#[serde(rename = "connectRPC")]
8992
connect_rpc: Option<bool>,
9093
},
@@ -220,9 +223,20 @@ impl Source<UnResolved> {
220223
is_mutation,
221224
})
222225
}
223-
Source::Proto { src, url, connect_rpc } => {
226+
Source::Proto { src, url, proto_paths, connect_rpc } => {
224227
let resolved_path = src.into_resolved(parent_dir);
225-
Ok(Source::Proto { src: resolved_path, url, connect_rpc })
228+
let resolved_proto_paths = proto_paths.map(|paths| {
229+
paths
230+
.into_iter()
231+
.map(|path| path.into_resolved(parent_dir))
232+
.collect()
233+
});
234+
Ok(Source::Proto {
235+
src: resolved_path,
236+
url,
237+
proto_paths: resolved_proto_paths,
238+
connect_rpc,
239+
})
226240
}
227241
Source::Config { src } => {
228242
let resolved_path = src.into_resolved(parent_dir);

src/cli/generator/generator.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,11 @@ impl Generator {
135135
headers: headers.into_btree_map(),
136136
});
137137
}
138-
Source::Proto { src, url, connect_rpc } => {
138+
Source::Proto { src, url, proto_paths, connect_rpc } => {
139139
let path = src.0;
140-
let mut metadata = proto_reader.read(&path).await?;
140+
let proto_paths =
141+
proto_paths.map(|paths| paths.into_iter().map(|l| l.0).collect::<Vec<_>>());
142+
let mut metadata = proto_reader.read(&path, proto_paths.as_deref()).await?;
141143
if let Some(relative_path_to_proto) = to_relative_path(output_dir, &path) {
142144
metadata.path = relative_path_to_proto;
143145
}

src/core/config/reader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl ConfigReader {
8787
}
8888
}
8989
LinkType::Protobuf => {
90-
let meta = self.proto_reader.read(path).await?;
90+
let meta = self.proto_reader.read(path, None).await?;
9191
extensions.add_proto(meta);
9292
}
9393
LinkType::Script => {

src/core/proto_reader/reader.rs

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,28 @@ impl ProtoReader {
6565

6666
/// Asynchronously reads all proto files from a list of paths
6767
pub async fn read_all<T: AsRef<str>>(&self, paths: &[T]) -> anyhow::Result<Vec<ProtoMetadata>> {
68-
let resolved_protos = join_all(paths.iter().map(|v| self.read(v.as_ref())))
68+
let resolved_protos = join_all(paths.iter().map(|v| self.read(v.as_ref(), None)))
6969
.await
7070
.into_iter()
7171
.collect::<anyhow::Result<Vec<_>>>()?;
7272
Ok(resolved_protos)
7373
}
7474

7575
/// Reads a proto file from a path
76-
pub async fn read<T: AsRef<str>>(&self, path: T) -> anyhow::Result<ProtoMetadata> {
77-
let file_read = self.read_proto(path.as_ref(), None).await?;
76+
pub async fn read<T: AsRef<str>>(
77+
&self,
78+
path: T,
79+
proto_paths: Option<&[String]>,
80+
) -> anyhow::Result<ProtoMetadata> {
81+
let file_read = self.read_proto(path.as_ref(), None, None).await?;
7882
Self::check_package(&file_read)?;
7983

8084
let descriptors = self
81-
.file_resolve(file_read, PathBuf::from(path.as_ref()).parent())
85+
.file_resolve(
86+
file_read,
87+
PathBuf::from(path.as_ref()).parent(),
88+
proto_paths,
89+
)
8290
.await?;
8391
let metadata = ProtoMetadata {
8492
descriptor_set: FileDescriptorSet { file: descriptors },
@@ -130,12 +138,22 @@ impl ProtoReader {
130138
&self,
131139
parent_proto: FileDescriptorProto,
132140
parent_path: Option<&Path>,
141+
proto_paths: Option<&[String]>,
133142
) -> anyhow::Result<Vec<FileDescriptorProto>> {
134143
self.resolve_dependencies(parent_proto, |import| {
135144
let parent_path = parent_path.map(|p| p.to_path_buf());
136145
let this = self.clone();
137-
138-
async move { this.read_proto(import, parent_path.as_deref()).await }.boxed()
146+
let proto_paths = proto_paths.map(|paths| {
147+
paths
148+
.iter()
149+
.map(|p| Path::new(p).to_path_buf())
150+
.collect::<Vec<_>>()
151+
});
152+
async move {
153+
this.read_proto(import, parent_path.as_deref(), proto_paths.as_deref())
154+
.await
155+
}
156+
.boxed()
139157
})
140158
.await
141159
}
@@ -159,27 +177,39 @@ impl ProtoReader {
159177
&self,
160178
path: T,
161179
parent_dir: Option<&Path>,
180+
proto_paths: Option<&[PathBuf]>,
162181
) -> anyhow::Result<FileDescriptorProto> {
163182
let content = if let Ok(file) = GoogleFileResolver::new().open_file(path.as_ref()) {
164183
file.source()
165184
.context("Unable to extract content of google well-known proto file")?
166185
.to_string()
167186
} else {
168-
let path = Self::resolve_path(path.as_ref(), parent_dir);
187+
let path = Self::resolve_path(path.as_ref(), parent_dir, proto_paths);
169188
self.reader.read_file(path).await?.content
170189
};
171190
Ok(protox_parse::parse(path.as_ref(), &content)?)
172191
}
173192
/// Checks if path is absolute else it joins file path with relative dir
174193
/// path
175-
fn resolve_path(src: &str, root_dir: Option<&Path>) -> String {
194+
fn resolve_path(src: &str, root_dir: Option<&Path>, proto_paths: Option<&[PathBuf]>) -> String {
176195
if src.starts_with("http") {
177196
return src.to_string();
178197
}
179198

180199
if Path::new(&src).is_absolute() {
181-
src.to_string()
182-
} else if let Some(path) = root_dir {
200+
return src.to_string();
201+
}
202+
203+
if let Some(proto_paths) = proto_paths {
204+
for proto_path in proto_paths {
205+
let path = proto_path.join(src);
206+
if path.exists() {
207+
return path.to_string_lossy().to_string();
208+
}
209+
}
210+
}
211+
212+
if let Some(path) = root_dir {
183213
path.join(src).to_string_lossy().to_string()
184214
} else {
185215
src.to_string()
@@ -210,7 +240,7 @@ mod test_proto_config {
210240
let runtime = crate::core::runtime::test::init(None);
211241
let reader = ProtoReader::init(ResourceReader::<Cached>::cached(runtime.clone()), runtime);
212242
reader
213-
.read_proto("google/protobuf/empty.proto", None)
243+
.read_proto("google/protobuf/empty.proto", None, None)
214244
.await
215245
.unwrap();
216246
}
@@ -225,7 +255,11 @@ mod test_proto_config {
225255

226256
let reader = ProtoReader::init(ResourceReader::<Cached>::cached(runtime.clone()), runtime);
227257
let file_descriptors = reader
228-
.file_resolve(reader.read_proto(&test_file, None).await?, Some(test_dir))
258+
.file_resolve(
259+
reader.read_proto(&test_file, None, None).await?,
260+
Some(test_dir),
261+
None,
262+
)
229263
.await?;
230264
for file in file_descriptors
231265
.iter()
@@ -248,7 +282,7 @@ mod test_proto_config {
248282
let reader = ProtoReader::init(ResourceReader::<Cached>::cached(runtime.clone()), runtime);
249283
let proto_no_pkg =
250284
PathBuf::from(tailcall_fixtures::configs::SELF).join("proto_no_pkg.graphql");
251-
let config_module = reader.read(proto_no_pkg.to_str().unwrap()).await;
285+
let config_module = reader.read(proto_no_pkg.to_str().unwrap(), None).await;
252286
assert!(config_module.is_err());
253287
Ok(())
254288
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
package news;
4+
5+
import "protobuf/news_dto.proto";
6+
import "google/protobuf/empty.proto";
7+
8+
service NewsService {
9+
rpc GetAllNews(google.protobuf.Empty) returns (NewsList) {}
10+
rpc GetNews(NewsId) returns (News) {}
11+
rpc GetMultipleNews(MultipleNewsId) returns (NewsList) {}
12+
rpc DeleteNews(NewsId) returns (google.protobuf.Empty) {}
13+
rpc EditNews(News) returns (News) {}
14+
rpc AddNews(News) returns (News) {}
15+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
```json @config
2+
{
3+
"inputs": [
4+
{
5+
"curl": {
6+
"src": "http://jsonplaceholder.typicode.com/users",
7+
"fieldName": "users"
8+
}
9+
},
10+
{
11+
"proto": {
12+
"src": "tailcall-fixtures/fixtures/protobuf/news_proto_paths.proto",
13+
"url": "http://localhost:50051",
14+
"protoPaths": ["tailcall-fixtures/fixtures/"]
15+
}
16+
}
17+
],
18+
"preset": {
19+
"mergeType": 1.0,
20+
"inferTypeNames": true,
21+
"treeShake": true
22+
},
23+
"output": {
24+
"path": "./output.graphql",
25+
"format": "graphQL"
26+
},
27+
"schema": {
28+
"query": "Query"
29+
}
30+
}
31+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
source: tests/cli/gen.rs
3+
expression: config.to_sdl()
4+
---
5+
schema @server @upstream {
6+
query: Query
7+
}
8+
9+
input GEN__news__MultipleNewsId {
10+
ids: [Id]
11+
}
12+
13+
input GEN__news__NewsInput {
14+
body: String
15+
id: Int
16+
postImage: String
17+
status: Status
18+
title: String
19+
}
20+
21+
input Id {
22+
id: Int
23+
}
24+
25+
enum Status {
26+
DELETED
27+
DRAFT
28+
PUBLISHED
29+
}
30+
31+
type Address {
32+
city: String
33+
geo: Geo
34+
street: String
35+
suite: String
36+
zipcode: String
37+
}
38+
39+
type Company {
40+
bs: String
41+
catchPhrase: String
42+
name: String
43+
}
44+
45+
type GEN__news__NewsList {
46+
news: [News]
47+
}
48+
49+
type Geo {
50+
lat: String
51+
lng: String
52+
}
53+
54+
type News {
55+
body: String
56+
id: Int
57+
postImage: String
58+
status: Status
59+
title: String
60+
}
61+
62+
type Query {
63+
GEN__news__NewsService__AddNews(news: GEN__news__NewsInput!): News @grpc(url: "http://localhost:50051", body: "{{.args.news}}", method: "news.NewsService.AddNews")
64+
GEN__news__NewsService__DeleteNews(newsId: Id!): Empty @grpc(url: "http://localhost:50051", body: "{{.args.newsId}}", method: "news.NewsService.DeleteNews")
65+
GEN__news__NewsService__EditNews(news: GEN__news__NewsInput!): News @grpc(url: "http://localhost:50051", body: "{{.args.news}}", method: "news.NewsService.EditNews")
66+
GEN__news__NewsService__GetAllNews: GEN__news__NewsList @grpc(url: "http://localhost:50051", method: "news.NewsService.GetAllNews")
67+
GEN__news__NewsService__GetMultipleNews(multipleNewsId: GEN__news__MultipleNewsId!): GEN__news__NewsList @grpc(url: "http://localhost:50051", body: "{{.args.multipleNewsId}}", method: "news.NewsService.GetMultipleNews")
68+
GEN__news__NewsService__GetNews(newsId: Id!): News @grpc(url: "http://localhost:50051", body: "{{.args.newsId}}", method: "news.NewsService.GetNews")
69+
users: [User] @http(url: "http://jsonplaceholder.typicode.com/users")
70+
}
71+
72+
type User {
73+
address: Address
74+
company: Company
75+
email: String
76+
id: Int
77+
name: String
78+
phone: String
79+
username: String
80+
website: String
81+
}

0 commit comments

Comments
 (0)