-
Notifications
You must be signed in to change notification settings - Fork 426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CBOR as response serialization #791
Comments
No, they're declared by GraphQL spec. If you override the spec, then you'll have something else, but not the GraphQL. But... defining your own custom |
@tyranron GraphQL use typically JSON but could use anything. The server response and the client variables could be serialized in CBOR too. In fact I'm looking for this feature too for IoT applications. https://graphql.org/learn/best-practices/#json-with-gzip To use, you would need enable a features=[..,"CBOR",...] in the cargo.toml and in the HTTP header send "Content-Type": "application/cbor". This feature will reduce the communication latency. |
Thanks @tyranron and @kranfix! I will have to give outputting values without coercing them into the GraphQL types a little hack. Might need some more assistance on that. @kranfix You might be interested in our solution. I guess you could say I'm working on IoT. A HTTP server was too heavy for our platform, so we instead use plain UDP, with GraphQL request and CBOR response. much lighter than requiring a Webserver. |
I think I've found my issue @tyranron. pub fn execute<'a, S, CtxT, QueryT, MutationT>(
document_source: &'a str,
operation_name: Option<&str>,
root_node: &'a RootNode<QueryT, MutationT, S>,
variables: &Variables<S>,
context: &CtxT
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>> where
S: ScalarValue,
&'b S: ScalarRefValue<'b>,
QueryT: GraphQLType<S, Context = CtxT>,
MutationT: GraphQLType<S, Context = CtxT>, The requirement on The requirement of |
@grantperry Sometimes I use GQL in MQTT or NATS for small queries. It also could be UDP to speed up the data transfer, but it is more used in local networks where the probability of data corruption is much less. |
@kranfix I will have a look into quiche. I've only heard about QUIC/h3 over last week or so. quiche doesn't look particularly idiomatic yet, but I like what it has to offer. |
@grantperry @tyranron the solution is not changing the Juniper core, but also the http adaptors (juniper_rocket, juniper_warp, etc). In the end, all adaptors call to @grantperry, with this change, it is possible to send data in CBOR format without a let (res, _errors) = juniper::execute(
"query user($id: String!){
user(id: $id){
id,
name
friends {
name
}
}
}",
None,
&schema,
// parsed variables: This comes from JSON but could come from another format like CBOR
&vec![("id".to_owned(), InputValue::scalar("xyz"))]
.into_iter()
.collect(),
&ctx,
)
.unwrap(); |
@kranfix I'm not sure were on the same wavelength? I'm not worried about the Request being in CBOR. Only the Response... This is my Request handler. The works perfectly fine, but the only scalar types returned by if let Ok((size, peer)) = socket.recv_from(&mut buf) {
if let Ok(query) = String::from_utf8(buf[0..size].to_vec()) {
let resp = match execute(
&query,
None,
&self.root_node,
&Variables::new(),
&self.context,
) {
Ok((val, errs)) => {
serde_cbor::to_vec(&CborGQLResponse {
data: val,
errors: errs,
})
.unwrap()
}
Err(e) => serde_cbor::to_vec(&CborGQLErrors { errors: e }).unwrap(),
};
if let Err(e) = socket.send_to(&resp, &peer) {
error!("Failed to send udp response: {:?}", e);
};
}
} |
@grantperry yes, but if it can be serialized when the server response, it also can be deserialize when the client make a request. For IoT is important both cases. I will make some experiments with |
@grantperry a After performing And given a custom struct MyInt(i32);
#[graphql_scalar]
impl GraphQLScalar for MyInt {
fn resolve(&self) -> Value<CborScalarValue> {
Value::scalar(self.0) // this is default one,
// but, surely, you can do here any direct conversions
// into `CborScalarValue` from your `MyInt` type
}
fn from_input_value(v: &InputValue<CborScalarValue>) -> Option<MyInt> {
v.as_scalar_value::<i32>().map(|i| MyInt(*i))
// Same here, you may use all the power of `CborScalarValue`
// to convert directly into your `MyInt` type.
}
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, CborScalarValue> {
<i32 as ParseScalarValue<CborScalarValue>>::from_str(value)
// And here we define how the `CborScalarValue` should be parsed from a `&str`
// for this particular `MyInt` case.
}
} So, with a |
It is not necessary to modify it. The GraphQLResponse must be serialized as cbor instead of json in the juniper-rocket crate for example. I'm working on it. I already modified the Graphiql to read cbor. Let me complete the example and I'll share my fork. |
I'm wondering if #782 hinder stuff like this? |
@davidpdrsn no. It is independent the |
I did it! It works! In this case, I modified the type OptionlSerializer<S = DefaultScalarValue> = Option<fn(&GraphQLBatchResponse<S>) -> String>;
impl<S> GraphQLRequest<S>
where
S: ScalarValue,
{
pub fn execute_sync<CtxT, QueryT, MutationT, SubscriptionT>(
&self,
root_node: &RootNode<QueryT, MutationT, SubscriptionT, S>,
context: &CtxT,
// HERE IS THE OPTIONAL CALLBACK
serializer: &OptionlSerializer<S>,
) -> GraphQLResponse;
pub async fn execute<CtxT, QueryT, MutationT, SubscriptionT>(
&self,
root_node: &RootNode<'_, QueryT, MutationT, SubscriptionT, S>,
context: &CtxT,
// HERE IS THE OPTIONAL CALLBACK AGAIN.
serializer: &OptionlSerializer<S>,
) -> GraphQLResponse
} And how is the callback invoked? let json = match serializer {
Some(ser) => ser(&response),
None => serde_json::to_string(&response).unwrap(),
};
GraphQLResponse(status, json) And in the example, we could define the callback. Now, It only is missing to read if the @tyranron I hope this approach could help to make |
I forgot to put the reference to the code: https://github.com/kranfix/juniper/tree/cbor |
I would love to handle this in a generic way. The core is explicitly json agnostic but the webserver integration crates were hardcoded to json as that is what 99% of people will use and the crates themselves are basically adapters that could be reimplemented. |
@LegNeato yes, It should be agnostic, but the current limitation is the lack of customization. |
Any updates on that? |
@Erik1000 I also want an update for this. |
I'm working in an embedded platform with low processing power and the need for very efficient data transfer, hence I have been using CBOR as the response serialization instead of JSON. This works very well and haven't come encountered any bugs. What I am trying to do now is use the more expansive Scalar type of CBOR instead of the default juniper Scalar.
I understand that I will likely need to impl ScalarValue on the CBOR Scalar, but this defeats the purpose of using CBOR for its more concise binary format, as I would have to convert all the concise scalar values to the primitive representation that GraphQL offers.
What I am trying to find out, is if there is a way of overriding the strict four type response of GraphQL? Big ask I know 😁
Look forward to your help!
The text was updated successfully, but these errors were encountered: