Skip to content

Commit

Permalink
feat(gen): support proto_paths for gen graphql through proto file (#3206
Browse files Browse the repository at this point in the history
)
  • Loading branch information
DLillard0 authored Dec 9, 2024
1 parent fe220be commit 858ac97
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 18 deletions.
18 changes: 16 additions & 2 deletions src/cli/generator/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,13 @@ pub enum Source<Status = UnResolved> {
is_mutation: Option<bool>,
field_name: String,
},
#[serde(rename_all = "camelCase")]
Proto {
src: Location<Status>,
url: String,
#[serde(skip_serializing_if = "Option::is_none")]
proto_paths: Option<Vec<Location<Status>>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "connectRPC")]
connect_rpc: Option<bool>,
},
Expand Down Expand Up @@ -220,9 +223,20 @@ impl Source<UnResolved> {
is_mutation,
})
}
Source::Proto { src, url, connect_rpc } => {
Source::Proto { src, url, proto_paths, connect_rpc } => {
let resolved_path = src.into_resolved(parent_dir);
Ok(Source::Proto { src: resolved_path, url, connect_rpc })
let resolved_proto_paths = proto_paths.map(|paths| {
paths
.into_iter()
.map(|path| path.into_resolved(parent_dir))
.collect()
});
Ok(Source::Proto {
src: resolved_path,
url,
proto_paths: resolved_proto_paths,
connect_rpc,
})
}
Source::Config { src } => {
let resolved_path = src.into_resolved(parent_dir);
Expand Down
6 changes: 4 additions & 2 deletions src/cli/generator/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ impl Generator {
headers: headers.into_btree_map(),
});
}
Source::Proto { src, url, connect_rpc } => {
Source::Proto { src, url, proto_paths, connect_rpc } => {
let path = src.0;
let mut metadata = proto_reader.read(&path).await?;
let proto_paths =
proto_paths.map(|paths| paths.into_iter().map(|l| l.0).collect::<Vec<_>>());
let mut metadata = proto_reader.read(&path, proto_paths.as_deref()).await?;
if let Some(relative_path_to_proto) = to_relative_path(output_dir, &path) {
metadata.path = relative_path_to_proto;
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/config/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl ConfigReader {
}
}
LinkType::Protobuf => {
let meta = self.proto_reader.read(path).await?;
let meta = self.proto_reader.read(path, None).await?;
extensions.add_proto(meta);
}
LinkType::Script => {
Expand Down
60 changes: 47 additions & 13 deletions src/core/proto_reader/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,28 @@ impl ProtoReader {

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

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

let descriptors = self
.file_resolve(file_read, PathBuf::from(path.as_ref()).parent())
.file_resolve(
file_read,
PathBuf::from(path.as_ref()).parent(),
proto_paths,
)
.await?;
let metadata = ProtoMetadata {
descriptor_set: FileDescriptorSet { file: descriptors },
Expand Down Expand Up @@ -130,12 +138,22 @@ impl ProtoReader {
&self,
parent_proto: FileDescriptorProto,
parent_path: Option<&Path>,
proto_paths: Option<&[String]>,
) -> anyhow::Result<Vec<FileDescriptorProto>> {
self.resolve_dependencies(parent_proto, |import| {
let parent_path = parent_path.map(|p| p.to_path_buf());
let this = self.clone();

async move { this.read_proto(import, parent_path.as_deref()).await }.boxed()
let proto_paths = proto_paths.map(|paths| {
paths
.iter()
.map(|p| Path::new(p).to_path_buf())
.collect::<Vec<_>>()
});
async move {
this.read_proto(import, parent_path.as_deref(), proto_paths.as_deref())
.await
}
.boxed()
})
.await
}
Expand All @@ -159,27 +177,39 @@ impl ProtoReader {
&self,
path: T,
parent_dir: Option<&Path>,
proto_paths: Option<&[PathBuf]>,
) -> anyhow::Result<FileDescriptorProto> {
let content = if let Ok(file) = GoogleFileResolver::new().open_file(path.as_ref()) {
file.source()
.context("Unable to extract content of google well-known proto file")?
.to_string()
} else {
let path = Self::resolve_path(path.as_ref(), parent_dir);
let path = Self::resolve_path(path.as_ref(), parent_dir, proto_paths);
self.reader.read_file(path).await?.content
};
Ok(protox_parse::parse(path.as_ref(), &content)?)
}
/// Checks if path is absolute else it joins file path with relative dir
/// path
fn resolve_path(src: &str, root_dir: Option<&Path>) -> String {
fn resolve_path(src: &str, root_dir: Option<&Path>, proto_paths: Option<&[PathBuf]>) -> String {
if src.starts_with("http") {
return src.to_string();
}

if Path::new(&src).is_absolute() {
src.to_string()
} else if let Some(path) = root_dir {
return src.to_string();
}

if let Some(proto_paths) = proto_paths {
for proto_path in proto_paths {
let path = proto_path.join(src);
if path.exists() {
return path.to_string_lossy().to_string();
}
}
}

if let Some(path) = root_dir {
path.join(src).to_string_lossy().to_string()
} else {
src.to_string()
Expand Down Expand Up @@ -210,7 +240,7 @@ mod test_proto_config {
let runtime = crate::core::runtime::test::init(None);
let reader = ProtoReader::init(ResourceReader::<Cached>::cached(runtime.clone()), runtime);
reader
.read_proto("google/protobuf/empty.proto", None)
.read_proto("google/protobuf/empty.proto", None, None)
.await
.unwrap();
}
Expand All @@ -225,7 +255,11 @@ mod test_proto_config {

let reader = ProtoReader::init(ResourceReader::<Cached>::cached(runtime.clone()), runtime);
let file_descriptors = reader
.file_resolve(reader.read_proto(&test_file, None).await?, Some(test_dir))
.file_resolve(
reader.read_proto(&test_file, None, None).await?,
Some(test_dir),
None,
)
.await?;
for file in file_descriptors
.iter()
Expand All @@ -248,7 +282,7 @@ mod test_proto_config {
let reader = ProtoReader::init(ResourceReader::<Cached>::cached(runtime.clone()), runtime);
let proto_no_pkg =
PathBuf::from(tailcall_fixtures::configs::SELF).join("proto_no_pkg.graphql");
let config_module = reader.read(proto_no_pkg.to_str().unwrap()).await;
let config_module = reader.read(proto_no_pkg.to_str().unwrap(), None).await;
assert!(config_module.is_err());
Ok(())
}
Expand Down
15 changes: 15 additions & 0 deletions tailcall-fixtures/fixtures/protobuf/news_proto_paths.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
syntax = "proto3";

package news;

import "protobuf/news_dto.proto";
import "google/protobuf/empty.proto";

service NewsService {
rpc GetAllNews(google.protobuf.Empty) returns (NewsList) {}
rpc GetNews(NewsId) returns (News) {}
rpc GetMultipleNews(MultipleNewsId) returns (NewsList) {}
rpc DeleteNews(NewsId) returns (google.protobuf.Empty) {}
rpc EditNews(News) returns (News) {}
rpc AddNews(News) returns (News) {}
}
31 changes: 31 additions & 0 deletions tests/cli/fixtures/generator/gen_proto_with_proto_paths_config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
```json @config
{
"inputs": [
{
"curl": {
"src": "http://jsonplaceholder.typicode.com/users",
"fieldName": "users"
}
},
{
"proto": {
"src": "tailcall-fixtures/fixtures/protobuf/news_proto_paths.proto",
"url": "http://localhost:50051",
"protoPaths": ["tailcall-fixtures/fixtures/"]
}
}
],
"preset": {
"mergeType": 1.0,
"inferTypeNames": true,
"treeShake": true
},
"output": {
"path": "./output.graphql",
"format": "graphQL"
},
"schema": {
"query": "Query"
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
source: tests/cli/gen.rs
expression: config.to_sdl()
---
schema @server @upstream {
query: Query
}

input GEN__news__MultipleNewsId {
ids: [Id]
}

input GEN__news__NewsInput {
body: String
id: Int
postImage: String
status: Status
title: String
}

input Id {
id: Int
}

enum Status {
DELETED
DRAFT
PUBLISHED
}

type Address {
city: String
geo: Geo
street: String
suite: String
zipcode: String
}

type Company {
bs: String
catchPhrase: String
name: String
}

type GEN__news__NewsList {
news: [News]
}

type Geo {
lat: String
lng: String
}

type News {
body: String
id: Int
postImage: String
status: Status
title: String
}

type Query {
GEN__news__NewsService__AddNews(news: GEN__news__NewsInput!): News @grpc(url: "http://localhost:50051", body: "{{.args.news}}", method: "news.NewsService.AddNews")
GEN__news__NewsService__DeleteNews(newsId: Id!): Empty @grpc(url: "http://localhost:50051", body: "{{.args.newsId}}", method: "news.NewsService.DeleteNews")
GEN__news__NewsService__EditNews(news: GEN__news__NewsInput!): News @grpc(url: "http://localhost:50051", body: "{{.args.news}}", method: "news.NewsService.EditNews")
GEN__news__NewsService__GetAllNews: GEN__news__NewsList @grpc(url: "http://localhost:50051", method: "news.NewsService.GetAllNews")
GEN__news__NewsService__GetMultipleNews(multipleNewsId: GEN__news__MultipleNewsId!): GEN__news__NewsList @grpc(url: "http://localhost:50051", body: "{{.args.multipleNewsId}}", method: "news.NewsService.GetMultipleNews")
GEN__news__NewsService__GetNews(newsId: Id!): News @grpc(url: "http://localhost:50051", body: "{{.args.newsId}}", method: "news.NewsService.GetNews")
users: [User] @http(url: "http://jsonplaceholder.typicode.com/users")
}

type User {
address: Address
company: Company
email: String
id: Int
name: String
phone: String
username: String
website: String
}

1 comment on commit 858ac97

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running 30s test @ http://localhost:8000/graphql

4 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev
Latency 3.87ms 1.50ms 21.19ms 68.80%
Req/Sec 6.51k 254.30 8.42k 90.00%

777276 requests in 30.02s, 3.90GB read

Requests/sec: 25895.97

Transfer/sec: 132.92MB

Please sign in to comment.