Skip to content

Commit

Permalink
feat: add grpc support for wasm (tailcallhq#1041)
Browse files Browse the repository at this point in the history
Co-authored-by: Sandipsinh Rathod <[email protected]>
Co-authored-by: Sandipsinh Rathod <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2024
1 parent 661fe7d commit fca6f7e
Show file tree
Hide file tree
Showing 36 changed files with 580 additions and 237 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ async-std = { version = "1.12.0", features = [
] }
ttl_cache = "0.5.1"
protox = "0.5.1"
protox-parse = "0.5.0"
prost-reflect = { version = "0.12.0", features = ["serde"] }
prost = "0.12.3"
update-informer = { version = "1.1.0", default-features = false, features = ["github", "reqwest"], optional = true }
Expand Down
1 change: 1 addition & 0 deletions cloudflare/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ async-graphql-value = "7.0.1"
serde_json = "1.0.113"
serde_qs = "0.12.0"
console_error_panic_hook = "0.1.7"
protox = "0.5.1"

[profile.release]
lto = true
Expand Down
2 changes: 1 addition & 1 deletion cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"deploy": "npx wrangler deploy --minify",
"publish": "npx wrangler publish --minify",
"dev": "npx wrangler dev --port 19194 --remote",
"dev": "npx wrangler dev --port 19194",
"test": "cargo install -q worker-build && worker-build && vitest --run"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions cloudflare/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ impl CloudflareFileIO {

// TODO: avoid the unsafe impl
unsafe impl Sync for CloudflareFileIO {}
unsafe impl Send for CloudflareFileIO {}

async fn get(bucket: Rc<worker::Bucket>, path: String) -> anyhow::Result<String> {
let maybe_object = bucket
Expand Down
5 changes: 3 additions & 2 deletions cloudflare/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use lazy_static::lazy_static;
use tailcall::async_graphql_hyper::GraphQLRequest;
use tailcall::blueprint::Blueprint;
use tailcall::config::reader::ConfigReader;
use tailcall::config::Config;
use tailcall::config::ConfigSet;
use tailcall::http::{graphiql, handle_request, AppContext};
use tailcall::EnvIO;

Expand Down Expand Up @@ -56,13 +56,14 @@ async fn get_config(
env_io: Arc<dyn EnvIO>,
env: Rc<worker::Env>,
file_path: &str,
) -> anyhow::Result<Config> {
) -> anyhow::Result<ConfigSet> {
let bucket_id = env_io
.get("BUCKET")
.ok_or(anyhow!("BUCKET var is not set"))?;
log::debug!("R2 Bucket ID: {}", bucket_id);
let file_io = init_file(env.clone(), bucket_id)?;
let http_io = init_http();

let reader = ConfigReader::init(file_io, http_io);
let config = reader.read(&file_path).await?;
Ok(config)
Expand Down
6 changes: 2 additions & 4 deletions cloudflare/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ impl HttpIO for CloudflareHttp {
}
}

pub async fn to_response(
response: hyper::Response<hyper::Body>,
) -> anyhow::Result<worker::Response> {
pub async fn to_response(response: hyper::Response<hyper::Body>) -> Result<worker::Response> {
let status = response.status().as_u16();
let headers = response.headers().clone();
let bytes = hyper::body::to_bytes(response).await?;
Expand Down Expand Up @@ -80,7 +78,7 @@ pub fn to_method(method: worker::Method) -> Result<hyper::Method> {
}
}

pub async fn to_request(mut req: worker::Request) -> anyhow::Result<hyper::Request<hyper::Body>> {
pub async fn to_request(mut req: worker::Request) -> Result<hyper::Request<hyper::Body>> {
let body = req.text().await.map_err(to_anyhow)?;
let method = req.method();
let uri = req.url().map_err(to_anyhow)?.as_str().to_string();
Expand Down
2 changes: 1 addition & 1 deletion cloudflare/wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ compatibility_date = "2023-03-22"
account_id = "59eda2a637301830ad43a6e3e4419346"

[build]
command = "cargo install -q worker-build && worker-build --release"
command = "cargo install -q worker-build && worker-build"

# the path to config must start with the binding name of respective r2 bucket.
[vars]
Expand Down
65 changes: 34 additions & 31 deletions src/blueprint/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ struct ProcessFieldWithinTypeContext<'a> {
remaining_path: &'a [String],
type_info: &'a config::Type,
is_required: bool,
config: &'a Config,
config_set: &'a ConfigSet,
invalid_path_handler: &'a InvalidPathHandler,
path_resolver_error_handler: &'a PathResolverErrorHandler,
original_path: &'a [String],
Expand All @@ -81,7 +81,7 @@ struct ProcessPathContext<'a> {
field: &'a config::Field,
type_info: &'a config::Type,
is_required: bool,
config: &'a Config,
config_set: &'a ConfigSet,
invalid_path_handler: &'a InvalidPathHandler,
path_resolver_error_handler: &'a PathResolverErrorHandler,
original_path: &'a [String],
Expand All @@ -93,7 +93,7 @@ fn process_field_within_type(context: ProcessFieldWithinTypeContext) -> Valid<Ty
let remaining_path = context.remaining_path;
let type_info = context.type_info;
let is_required = context.is_required;
let config = context.config;
let config_set = context.config_set;
let invalid_path_handler = context.invalid_path_handler;
let path_resolver_error_handler = context.path_resolver_error_handler;

Expand All @@ -119,7 +119,7 @@ fn process_field_within_type(context: ProcessFieldWithinTypeContext) -> Valid<Ty
.and(process_path(ProcessPathContext {
type_info,
is_required,
config,
config_set,
invalid_path_handler,
path_resolver_error_handler,
path: remaining_path,
Expand All @@ -132,7 +132,7 @@ fn process_field_within_type(context: ProcessFieldWithinTypeContext) -> Valid<Ty
if is_scalar(&next_field.type_of) {
return process_path(ProcessPathContext {
type_info,
config,
config_set,
invalid_path_handler,
path_resolver_error_handler,
path: remaining_path,
Expand All @@ -142,9 +142,9 @@ fn process_field_within_type(context: ProcessFieldWithinTypeContext) -> Valid<Ty
});
}

if let Some(next_type_info) = config.find_type(&next_field.type_of) {
if let Some(next_type_info) = config_set.find_type(&next_field.type_of) {
return process_path(ProcessPathContext {
config,
config_set,
invalid_path_handler,
path_resolver_error_handler,
path: remaining_path,
Expand All @@ -168,7 +168,7 @@ fn process_field_within_type(context: ProcessFieldWithinTypeContext) -> Valid<Ty
field,
type_info,
is_required,
config,
config_set,
invalid_path_handler,
path_resolver_error_handler,
original_path: context.original_path,
Expand All @@ -185,15 +185,15 @@ fn process_path(context: ProcessPathContext) -> Valid<Type, String> {
let field = context.field;
let type_info = context.type_info;
let is_required = context.is_required;
let config = context.config;
let config_set = context.config_set;
let invalid_path_handler = context.invalid_path_handler;
let path_resolver_error_handler = context.path_resolver_error_handler;
if let Some((field_name, remaining_path)) = path.split_first() {
if field_name.parse::<usize>().is_ok() {
let mut modified_field = field.clone();
modified_field.list = false;
return process_path(ProcessPathContext {
config,
config_set,
type_info,
invalid_path_handler,
path_resolver_error_handler,
Expand All @@ -207,7 +207,7 @@ fn process_path(context: ProcessPathContext) -> Valid<Type, String> {
.fields
.get(field_name)
.map(|_| type_info)
.or_else(|| config.find_type(&field.type_of));
.or_else(|| config_set.find_type(&field.type_of));

if let Some(type_info) = target_type_info {
return process_field_within_type(ProcessFieldWithinTypeContext {
Expand All @@ -216,7 +216,7 @@ fn process_path(context: ProcessPathContext) -> Valid<Type, String> {
remaining_path,
type_info,
is_required,
config,
config_set,
invalid_path_handler,
path_resolver_error_handler,
original_path: context.original_path,
Expand Down Expand Up @@ -252,9 +252,9 @@ fn to_enum_type_definition(
fn to_object_type_definition(
name: &str,
type_of: &config::Type,
config: &Config,
config_set: &ConfigSet,
) -> Valid<Definition, String> {
to_fields(name, type_of, config).map(|fields| {
to_fields(name, type_of, config_set).map(|fields| {
Definition::ObjectTypeDefinition(ObjectTypeDefinition {
name: name.to_string(),
description: type_of.doc.clone(),
Expand All @@ -266,8 +266,8 @@ fn to_object_type_definition(

fn update_args<'a>(
hasher: DefaultHasher,
) -> TryFold<'a, (&'a Config, &'a Field, &'a config::Type, &'a str), FieldDefinition, String> {
TryFold::<(&Config, &Field, &config::Type, &str), FieldDefinition, String>::new(
) -> TryFold<'a, (&'a ConfigSet, &'a Field, &'a config::Type, &'a str), FieldDefinition, String> {
TryFold::<(&ConfigSet, &Field, &config::Type, &str), FieldDefinition, String>::new(
move |(_, field, typ, name), _| {
let mut hasher = hasher.clone();
name.hash(&mut hasher);
Expand Down Expand Up @@ -333,8 +333,8 @@ fn update_resolver_from_path(
/// To solve the problem that by default such fields will be resolved to null value
/// and nested resolvers won't be called
pub fn update_nested_resolvers<'a>(
) -> TryFold<'a, (&'a Config, &'a Field, &'a config::Type, &'a str), FieldDefinition, String> {
TryFold::<(&Config, &Field, &config::Type, &str), FieldDefinition, String>::new(
) -> TryFold<'a, (&'a ConfigSet, &'a Field, &'a config::Type, &'a str), FieldDefinition, String> {
TryFold::<(&ConfigSet, &Field, &config::Type, &str), FieldDefinition, String>::new(
move |(config, field, _, name), mut b_field| {
if !field.has_resolver()
&& validate_field_has_resolver(name, field, &config.types).is_succeed()
Expand All @@ -361,9 +361,9 @@ fn validate_field_type_exist(config: &Config, field: &Field) -> Valid<(), String
fn to_fields(
object_name: &str,
type_of: &config::Type,
config: &Config,
config_set: &ConfigSet,
) -> Valid<Vec<FieldDefinition>, String> {
let operation_type = if config.schema.mutation.as_deref().eq(&Some(object_name)) {
let operation_type = if config_set.schema.mutation.as_deref().eq(&Some(object_name)) {
GraphQLOperationType::Mutation
} else {
GraphQLOperationType::Query
Expand All @@ -390,7 +390,10 @@ fn to_fields(
.and(update_expr(&operation_type).trace(config::Expr::trace_name().as_str()))
.and(update_modify().trace(config::Modify::trace_name().as_str()))
.and(update_nested_resolvers())
.try_fold(&(config, field, type_of, name), FieldDefinition::default())
.try_fold(
&(config_set, field, type_of, name),
FieldDefinition::default(),
)
};

// Process fields that are not marked as `omit`
Expand All @@ -400,7 +403,7 @@ fn to_fields(
.iter()
.filter(|(_, field)| !field.is_omitted()),
|(name, field)| {
validate_field_type_exist(config, field)
validate_field_type_exist(config_set, field)
.and(to_field(name, field))
.trace(name)
},
Expand Down Expand Up @@ -455,7 +458,7 @@ fn to_fields(
field: source_field,
type_info: type_of,
is_required: false,
config,
config_set,
invalid_path_handler: &invalid_path_handler,
path_resolver_error_handler: &path_resolver_error_handler,
original_path: &add_field.path,
Expand All @@ -481,11 +484,11 @@ fn to_fields(
})
}

pub fn to_definitions<'a>() -> TryFold<'a, Config, Vec<Definition>, String> {
TryFold::<Config, Vec<Definition>, String>::new(|config, _| {
let output_types = config.output_types();
let input_types = config.input_types();
Valid::from_iter(config.types.iter(), |(name, type_)| {
pub fn to_definitions<'a>() -> TryFold<'a, ConfigSet, Vec<Definition>, String> {
TryFold::<ConfigSet, Vec<Definition>, String>::new(|config_set, _| {
let output_types = config_set.output_types();
let input_types = config_set.input_types();
Valid::from_iter(config_set.types.iter(), |(name, type_)| {
let dbl_usage = input_types.contains(name) && output_types.contains(name);
if let Some(variants) = &type_.variants {
if !variants.is_empty() {
Expand All @@ -498,11 +501,11 @@ pub fn to_definitions<'a>() -> TryFold<'a, Config, Vec<Definition>, String> {
} else if dbl_usage {
Valid::fail("type is used in input and output".to_string()).trace(name)
} else {
to_object_type_definition(name, type_, config)
to_object_type_definition(name, type_, config_set)
.trace(name)
.and_then(|definition| match definition.clone() {
Definition::ObjectTypeDefinition(object_type_definition) => {
if config.input_types().contains(name) {
if config_set.input_types().contains(name) {
to_input_object_type_definition(object_type_definition).trace(name)
} else if type_.interface {
to_interface_type_definition(object_type_definition).trace(name)
Expand All @@ -515,7 +518,7 @@ pub fn to_definitions<'a>() -> TryFold<'a, Config, Vec<Definition>, String> {
}
})
.map(|mut types| {
types.extend(config.unions.iter().map(to_union_type_definition));
types.extend(config_set.unions.iter().map(to_union_type_definition));
types
})
})
Expand Down
15 changes: 8 additions & 7 deletions src/blueprint/from_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ use std::collections::{BTreeMap, HashMap};
use super::{Server, TypeLike};
use crate::blueprint::compress::compress;
use crate::blueprint::*;
use crate::config::{Arg, Batch, Config, Field};
use crate::config::{Arg, Batch, Config, ConfigSet, Field};
use crate::json::JsonSchema;
use crate::lambda::{Expression, IO};
use crate::try_fold::TryFold;
use crate::valid::{Valid, ValidationError};

pub fn config_blueprint<'a>() -> TryFold<'a, Config, Blueprint, String> {
let server = TryFoldConfig::<Blueprint>::new(|config, blueprint| {
Valid::from(Server::try_from(config.server.clone())).map(|server| blueprint.server(server))
pub fn config_blueprint<'a>() -> TryFold<'a, ConfigSet, Blueprint, String> {
let server = TryFoldConfig::<Blueprint>::new(|config_set, blueprint| {
Valid::from(Server::try_from(config_set.server.clone()))
.map(|server| blueprint.server(server))
});

let schema = to_schema().transform::<Blueprint>(
Expand Down Expand Up @@ -105,12 +106,12 @@ where
}
}

impl TryFrom<&Config> for Blueprint {
impl TryFrom<&ConfigSet> for Blueprint {
type Error = ValidationError<String>;

fn try_from(config: &Config) -> Result<Self, Self::Error> {
fn try_from(config_set: &ConfigSet) -> Result<Self, Self::Error> {
config_blueprint()
.try_fold(config, Blueprint::default())
.try_fold(config_set, Blueprint::default())
.to_result()
}
}
4 changes: 2 additions & 2 deletions src/blueprint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ pub use server::*;
pub use timeout::GlobalTimeout;
pub use upstream::*;

use crate::config::{Arg, Config, Field};
use crate::config::{Arg, ConfigSet, Field};
use crate::try_fold::TryFold;

pub type TryFoldConfig<'a, A> = TryFold<'a, Config, A, String>;
pub type TryFoldConfig<'a, A> = TryFold<'a, ConfigSet, A, String>;

pub(crate) trait TypeLike {
fn name(&self) -> &str;
Expand Down
Loading

0 comments on commit fca6f7e

Please sign in to comment.