Skip to content

Commit

Permalink
Merge branch 'main' into fix/not-allow-add-field-on-input-types
Browse files Browse the repository at this point in the history
  • Loading branch information
laststylebender14 authored Dec 5, 2024
2 parents ac5a411 + 51ebc1f commit a2cd8d2
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 8 deletions.
7 changes: 5 additions & 2 deletions src/cli/generator/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ pub enum Source<Status = UnResolved> {
Proto {
src: Location<Status>,
url: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "connectRPC")]
connect_rpc: Option<bool>,
},
Config {
src: Location<Status>,
Expand Down Expand Up @@ -217,9 +220,9 @@ impl Source<UnResolved> {
is_mutation,
})
}
Source::Proto { src, url } => {
Source::Proto { src, url, connect_rpc } => {
let resolved_path = src.into_resolved(parent_dir);
Ok(Source::Proto { src: resolved_path, url })
Ok(Source::Proto { src: resolved_path, url, connect_rpc })
}
Source::Config { src } => {
let resolved_path = src.into_resolved(parent_dir);
Expand Down
4 changes: 2 additions & 2 deletions src/cli/generator/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,13 @@ impl Generator {
headers: headers.into_btree_map(),
});
}
Source::Proto { src, url } => {
Source::Proto { src, url, connect_rpc } => {
let path = src.0;
let mut metadata = proto_reader.read(&path).await?;
if let Some(relative_path_to_proto) = to_relative_path(output_dir, &path) {
metadata.path = relative_path_to_proto;
}
input_samples.push(Input::Proto { metadata, url });
input_samples.push(Input::Proto { metadata, url, connect_rpc });
}
Source::Config { src } => {
let path = src.0;
Expand Down
1 change: 1 addition & 0 deletions src/core/config/transformer/ambiguous_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ mod tests {
.inputs(vec![Input::Proto {
metadata: ProtoMetadata { descriptor_set: set, path: news_proto.to_string() },
url,
connect_rpc: None,
}])
.generate(false)?;

Expand Down
2 changes: 1 addition & 1 deletion src/core/config/transformer/preset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Preset {
Self {
merge_type: 0.0,
tree_shake: false,
infer_type_names: false,
infer_type_names: true,
unwrap_single_field_types: true,
}
}
Expand Down
15 changes: 12 additions & 3 deletions src/core/generator/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use tailcall_valid::Validator;
use url::Url;

use super::from_proto::from_proto;
use super::proto::connect_rpc::ConnectRPC;
use super::{FromJsonGenerator, NameGenerator, RequestSample, PREFIX};
use crate::core::config::{self, Config, ConfigModule, Link, LinkType};
use crate::core::http::Method;
Expand Down Expand Up @@ -42,6 +43,7 @@ pub enum Input {
Proto {
url: String,
metadata: ProtoMetadata,
connect_rpc: Option<bool>,
},
Config {
schema: String,
Expand Down Expand Up @@ -133,9 +135,14 @@ impl Generator {
config = config
.merge_right(self.generate_from_json(&type_name_generator, &[req_sample])?);
}
Input::Proto { metadata, url } => {
config =
config.merge_right(self.generate_from_proto(metadata, &self.query, url)?);
Input::Proto { metadata, url, connect_rpc } => {
let proto_config = self.generate_from_proto(metadata, &self.query, url)?;
let proto_config = if connect_rpc == &Some(true) {
ConnectRPC.transform(proto_config).to_result()?
} else {
proto_config
};
config = config.merge_right(proto_config);
}
}
}
Expand Down Expand Up @@ -264,6 +271,7 @@ pub mod test {
path: "../../../tailcall-fixtures/fixtures/protobuf/news.proto".to_string(),
},
url: "http://localhost:50051".to_string(),
connect_rpc: None,
}])
.generate(false)?;

Expand Down Expand Up @@ -317,6 +325,7 @@ pub mod test {
path: "../../../tailcall-fixtures/fixtures/protobuf/news.proto".to_string(),
},
url: "http://localhost:50051".to_string(),
connect_rpc: None,
};

// Config input
Expand Down
175 changes: 175 additions & 0 deletions src/core/generator/proto/connect_rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use tailcall_valid::Valid;

use crate::core::config::{Config, Grpc, Http, Resolver, ResolverSet};
use crate::core::Transform;

pub struct ConnectRPC;

impl Transform for ConnectRPC {
type Value = Config;
type Error = String;

fn transform(&self, mut config: Self::Value) -> Valid<Self::Value, Self::Error> {
for type_ in config.types.values_mut() {
for field_ in type_.fields.values_mut() {
let new_resolvers = field_
.resolvers
.0
.iter()
.map(|resolver| match resolver {
Resolver::Grpc(grpc) => Resolver::Http(Http::from(grpc.clone())),
other => other.clone(),
})
.collect();

field_.resolvers = ResolverSet(new_resolvers);
}
}

Valid::succeed(config)
}
}

impl From<Grpc> for Http {
fn from(grpc: Grpc) -> Self {
let url = grpc.url;
let body = grpc.body.or_else(|| {
// if body isn't present while transforming the resolver, we need to provide an
// empty object.
Some(serde_json::Value::Object(serde_json::Map::new()))
});

// remove the last
// method: package.service.method
// remove the method from the end.
let parts = grpc.method.split(".").collect::<Vec<_>>();
let method = parts[..parts.len() - 1].join(".").to_string();
let endpoint = parts[parts.len() - 1].to_string();

let new_url = format!("{}/{}/{}", url, method, endpoint);
let headers = grpc.headers;
let batch_key = grpc.batch_key;
let dedupe = grpc.dedupe;
let select = grpc.select;
let on_response_body = grpc.on_response_body;

Self {
url: new_url,
body: body.map(|b| b.to_string()),
method: crate::core::http::Method::POST,
headers,
batch_key,
dedupe,
select,
on_response_body,
..Default::default()
}
}
}

#[cfg(test)]
mod tests {
use serde_json::{json, Value};

use super::*;
use crate::core::config::KeyValue;

#[test]
fn test_grpc_to_http_basic_conversion() {
let grpc = Grpc {
url: "http://localhost:8080".to_string(),
method: "package.service.method".to_string(),
body: Some(json!({"key": "value"})),
headers: Default::default(),
batch_key: Default::default(),
dedupe: Default::default(),
select: Default::default(),
on_response_body: Default::default(),
};

let http = Http::from(grpc);

assert_eq!(http.url, "http://localhost:8080/package.service/method");
assert_eq!(http.method, crate::core::http::Method::POST);
assert_eq!(http.body, Some(r#"{"key":"value"}"#.to_string()));
}

#[test]
fn test_grpc_to_http_empty_body() {
let grpc = Grpc {
url: "http://localhost:8080".to_string(),
method: "package.service.method".to_string(),
body: Default::default(),
headers: Default::default(),
batch_key: Default::default(),
dedupe: Default::default(),
select: Default::default(),
on_response_body: Default::default(),
};

let http = Http::from(grpc);

assert_eq!(http.body, Some("{}".to_string()));
}

#[test]
fn test_grpc_to_http_with_headers() {
let grpc = Grpc {
url: "http://localhost:8080".to_string(),
method: "a.b.c".to_string(),
body: None,
headers: vec![KeyValue { key: "X-Foo".to_string(), value: "bar".to_string() }],
batch_key: Default::default(),
dedupe: Default::default(),
select: Default::default(),
on_response_body: Default::default(),
};

let http = Http::from(grpc);

assert_eq!(http.url, "http://localhost:8080/a.b/c");
assert_eq!(
http.headers
.iter()
.find(|h| h.key == "X-Foo")
.unwrap()
.value,
"bar".to_string()
);
}

#[test]
fn test_grpc_to_http_all_fields() {
let grpc = Grpc {
url: "http://localhost:8080".to_string(),
method: "package.service.method".to_string(),
body: Some(json!({"key": "value"})),
headers: vec![KeyValue { key: "X-Foo".to_string(), value: "bar".to_string() }],
batch_key: vec!["batch_key_value".to_string()],
dedupe: Some(true),
select: Some(Value::String("select_value".to_string())),
on_response_body: Some("on_response_body_value".to_string()),
};

let http = Http::from(grpc);

assert_eq!(http.url, "http://localhost:8080/package.service/method");
assert_eq!(http.method, crate::core::http::Method::POST);
assert_eq!(http.body, Some(r#"{"key":"value"}"#.to_string()));
assert_eq!(
http.headers
.iter()
.find(|h| h.key == "X-Foo")
.unwrap()
.value,
"bar".to_string()
);
assert_eq!(http.batch_key, vec!["batch_key_value".to_string()]);
assert_eq!(http.dedupe, Some(true));
assert_eq!(http.select, Some(Value::String("select_value".to_string())));
assert_eq!(
http.on_response_body,
Some("on_response_body_value".to_string())
);
}
}
1 change: 1 addition & 0 deletions src/core/generator/proto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod comments_builder;
pub mod connect_rpc;
pub mod path_builder;
pub mod path_field;
31 changes: 31 additions & 0 deletions tests/cli/fixtures/generator/proto-connect-rpc.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",
"url": "http://localhost:50051",
"connectRPC": true
}
}
],
"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 @addField(name: "ids", path: ["ids", "id"]) {
ids: [Id]@omit
}

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 @http(url: "http://localhost:50051/news.NewsService/AddNews", body: "\"{{.args.news}}\"", method: "POST")
GEN__news__NewsService__DeleteNews(newsId: Id!): Empty @http(url: "http://localhost:50051/news.NewsService/DeleteNews", body: "\"{{.args.newsId}}\"", method: "POST")
GEN__news__NewsService__EditNews(news: GEN__news__NewsInput!): News @http(url: "http://localhost:50051/news.NewsService/EditNews", body: "\"{{.args.news}}\"", method: "POST")
GEN__news__NewsService__GetAllNews: GEN__news__NewsList @http(url: "http://localhost:50051/news.NewsService/GetAllNews", body: "{}", method: "POST")
GEN__news__NewsService__GetMultipleNews(multipleNewsId: GEN__news__MultipleNewsId!): GEN__news__NewsList @http(url: "http://localhost:50051/news.NewsService/GetMultipleNews", body: "\"{{.args.multipleNewsId}}\"", method: "POST")
GEN__news__NewsService__GetNews(newsId: Id!): News @http(url: "http://localhost:50051/news.NewsService/GetNews", body: "\"{{.args.newsId}}\"", method: "POST")
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
}

0 comments on commit a2cd8d2

Please sign in to comment.