Skip to content

Commit

Permalink
introduce `fn evaluate<T: Serialize>(&self, value: &T) -> async_graph…
Browse files Browse the repository at this point in the history
…ql::Value`
  • Loading branch information
ssddOnTop committed Apr 27, 2024
1 parent 73c0d9a commit 3369838
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 64 deletions.
16 changes: 12 additions & 4 deletions benches/request_template_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::borrow::Cow;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use derive_setters::Setters;
use hyper::HeaderMap;
use serde::{Serialize, Serializer};
use serde_json::json;
use tailcall::endpoint::Endpoint;
use tailcall::has_headers::HasHeaders;
Expand All @@ -20,14 +21,21 @@ impl Default for Context {
Self { value: serde_json::Value::Null, headers: HeaderMap::new() }
}
}

impl Serialize for Context {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_none()
// self.value.serialize(serializer)
}
}

impl PathString for Context {
fn path_string<T: AsRef<str>>(&self, parts: &[T]) -> Option<Cow<'_, str>> {
self.value.path_string(parts)
}

fn evaluate(&self, _filter: &jaq_interpret::Filter) -> Option<async_graphql::Value> {
None
}
}
impl HasHeaders for Context {
fn headers(&self) -> &HeaderMap {
Expand Down
15 changes: 10 additions & 5 deletions src/config/reader_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow;
use std::collections::BTreeMap;
use std::sync::Arc;

use jaq_interpret::Filter;
use serde::{Serialize, Serializer};

use crate::path::PathString;
use crate::EnvIO;
Expand All @@ -12,6 +12,15 @@ pub struct ConfigReaderContext<'a> {
pub vars: &'a BTreeMap<String, String>,
}

impl<'a> Serialize for ConfigReaderContext<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_none()
}
}

impl<'a> PathString for ConfigReaderContext<'a> {
fn path_string<T: AsRef<str>>(&self, path: &[T]) -> Option<Cow<'_, str>> {
if path.is_empty() {
Expand All @@ -25,10 +34,6 @@ impl<'a> PathString for ConfigReaderContext<'a> {
_ => None,
})
}

fn evaluate(&self, _filter: &Filter) -> Option<async_graphql::Value> {
None
}
}

#[cfg(test)]
Expand Down
15 changes: 11 additions & 4 deletions src/grpc/request_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ mod tests {
use hyper::header::{HeaderName, HeaderValue};
use hyper::{HeaderMap, Method};
use pretty_assertions::assert_eq;
use serde::{Serialize, Serializer};
use tailcall_fixtures::protobuf;

use super::RequestTemplate;
Expand Down Expand Up @@ -185,14 +186,20 @@ mod tests {
}
}

impl Serialize for Context {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_none()
// self.value.serialize(serializer)
}

Check warning on line 196 in src/grpc/request_template.rs

View check run for this annotation

Codecov / codecov/patch

src/grpc/request_template.rs#L190-L196

Added lines #L190 - L196 were not covered by tests
}

impl crate::path::PathString for Context {
fn path_string<T: AsRef<str>>(&self, parts: &[T]) -> Option<Cow<'_, str>> {
self.value.path_string(parts)
}

fn evaluate(&self, _filter: &jaq_interpret::Filter) -> Option<async_graphql::Value> {
todo!()
}
}

impl crate::has_headers::HasHeaders for Context {
Expand Down
17 changes: 12 additions & 5 deletions src/http/request_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ mod tests {
use hyper::header::HeaderName;
use hyper::HeaderMap;
use pretty_assertions::assert_eq;
use serde::{Serialize, Serializer};
use serde_json::json;

use super::RequestTemplate;
Expand All @@ -282,13 +283,19 @@ mod tests {
}
}

impl crate::path::PathString for Context {
fn path_string<T: AsRef<str>>(&self, parts: &[T]) -> Option<Cow<'_, str>> {
self.value.path_string(parts)
impl Serialize for Context {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_none()
// self.value.serialize(serializer)
}
}

fn evaluate(&self, _filter: &jaq_interpret::Filter) -> Option<async_graphql::Value> {
None
impl PathString for Context {
fn path_string<T: AsRef<str>>(&self, parts: &[T]) -> Option<Cow<'_, str>> {
self.value.path_string(parts)
}
}

Expand Down
68 changes: 49 additions & 19 deletions src/mustache.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::fmt::Display;

use jaq_interpret::FilterT;
use nom::branch::alt;
use nom::bytes::complete::{tag, take_until};
use nom::character::complete::char;
use nom::combinator::map;
use nom::multi::many0;
use nom::sequence::delimited;
use nom::{Finish, IResult};
use serde::Serialize;

use crate::path::{PathGraphql, PathString};

Expand Down Expand Up @@ -61,17 +63,33 @@ impl Mustache {
.collect();

if val.is_empty() {
self.evaluate(value)
self.evaluate_inner(value)
.map(|v| v.to_string())
.unwrap_or_default()
} else {
val
}
}

fn evaluate(&self, value: &impl PathString) -> String {
value
.evaluate(&self.jacques)
.map(|v| v.to_string())
.unwrap_or_default()
// TODO: Null converts to "null" as string but it should be empty string
// fn evaluate<T: Serialize>(&self, value: &T) -> async_graphql::Value {
// self.evaluate_inner(value).unwrap_or_default()
// }

fn evaluate_inner<T: Serialize>(&self, value: &T) -> Option<async_graphql::Value> {
let iter = jaq_interpret::RcIter::new(vec![].into_iter());
let value = serde_json::to_value(value).ok()?;
if value.is_null() {
return None;
}
let mut result = self.jacques.run((
jaq_interpret::Ctx::new(vec![], &iter),
jaq_interpret::Val::from(value),
));
let result = result.next()?;
let result = result.ok()?;
let result = async_graphql::Value::from(result.to_string());
Some(result)

Check warning on line 92 in src/mustache.rs

View check run for this annotation

Codecov / codecov/patch

src/mustache.rs#L85-L92

Added lines #L85 - L92 were not covered by tests
}

pub fn render_graphql(&self, value: &impl PathGraphql) -> String {
Expand Down Expand Up @@ -416,7 +434,7 @@ mod tests {
mod render {
use std::borrow::Cow;

use jaq_interpret::Filter;
use serde::{Serialize, Serializer};
use serde_json::json;

use crate::mustache::{Mustache, Segment};
Expand All @@ -435,6 +453,14 @@ mod tests {
fn test_render_mixed() {
struct DummyPath;

impl Serialize for DummyPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str("")
}

Check warning on line 462 in src/mustache.rs

View check run for this annotation

Codecov / codecov/patch

src/mustache.rs#L458-L462

Added lines #L458 - L462 were not covered by tests
}
impl PathString for DummyPath {
fn path_string<T: AsRef<str>>(&self, parts: &[T]) -> Option<Cow<'_, str>> {
let parts: Vec<&str> = parts.iter().map(AsRef::as_ref).collect();
Expand All @@ -447,10 +473,6 @@ mod tests {
None
}
}

fn evaluate(&self, _filter: &Filter) -> Option<async_graphql::Value> {
None // TODO
}
}

let mustache = Mustache::from(vec![
Expand All @@ -471,14 +493,18 @@ mod tests {
fn test_render_with_missing_path() {
struct DummyPath;

impl Serialize for DummyPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str("")
}

Check warning on line 502 in src/mustache.rs

View check run for this annotation

Codecov / codecov/patch

src/mustache.rs#L498-L502

Added lines #L498 - L502 were not covered by tests
}
impl PathString for DummyPath {
fn path_string<T: AsRef<str>>(&self, _: &[T]) -> Option<Cow<'_, str>> {
None
}

fn evaluate(&self, _filter: &Filter) -> Option<async_graphql::Value> {
None
}
}

let mustache = Mustache::from(vec![
Expand Down Expand Up @@ -511,6 +537,14 @@ mod tests {
fn test_render_preserves_spaces() {
struct DummyPath;

impl Serialize for DummyPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str("")
}

Check warning on line 546 in src/mustache.rs

View check run for this annotation

Codecov / codecov/patch

src/mustache.rs#L542-L546

Added lines #L542 - L546 were not covered by tests
}
impl PathString for DummyPath {
fn path_string<T: AsRef<str>>(&self, parts: &[T]) -> Option<Cow<'_, str>> {
let parts: Vec<&str> = parts.iter().map(AsRef::as_ref).collect();
Expand All @@ -521,10 +555,6 @@ mod tests {
None
}
}

fn evaluate(&self, _filter: &Filter) -> Option<async_graphql::Value> {
todo!()
}
}

let mustache = Mustache::from(vec![
Expand Down
41 changes: 14 additions & 27 deletions src/path.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Cow;

use jaq_interpret::{Filter, FilterT};
use serde::{Serialize, Serializer};
use serde_json::json;

use crate::json::JsonLike;
Expand All @@ -14,9 +14,8 @@ use crate::lambda::{EvaluationContext, ResolverContextLike};
/// The PathString trait provides a method for accessing values from a JSON-like
/// structure. The returned value is encoded as a plain string.
/// This is typically used in evaluating mustache templates.
pub trait PathString {
pub trait PathString: Serialize {
fn path_string<T: AsRef<str>>(&self, path: &[T]) -> Option<Cow<'_, str>>;
fn evaluate(&self, filter: &Filter) -> Option<async_graphql::Value>;
}

///
Expand All @@ -33,18 +32,6 @@ impl PathString for serde_json::Value {
_ => Cow::Owned(a.to_string()),
})
}

fn evaluate(&self, filter: &Filter) -> Option<async_graphql::Value> {
let iter = jaq_interpret::RcIter::new(vec![].into_iter());
let mut result = filter.run((
jaq_interpret::Ctx::new(vec![], &iter),
jaq_interpret::Val::from(self.clone()),
));
let result = result.next()?;
let result = result.ok()?;
let result = async_graphql::Value::from(result.to_string());
Some(result)
}
}

fn convert_value(value: Cow<'_, async_graphql::Value>) -> Option<Cow<'_, str>> {
Expand All @@ -63,6 +50,18 @@ fn convert_value(value: Cow<'_, async_graphql::Value>) -> Option<Cow<'_, str>> {
}
}

impl<'a, Ctx: ResolverContextLike<'a>> Serialize for EvaluationContext<'a, Ctx> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.value()
.cloned()
.unwrap_or_default()
.serialize(serializer)
}
}

impl<'a, Ctx: ResolverContextLike<'a>> PathString for EvaluationContext<'a, Ctx> {
fn path_string<T: AsRef<str>>(&self, path: &[T]) -> Option<Cow<'_, str>> {
let ctx = self;
Expand Down Expand Up @@ -90,18 +89,6 @@ impl<'a, Ctx: ResolverContextLike<'a>> PathString for EvaluationContext<'a, Ctx>
_ => None,
})
}

fn evaluate(&self, filter: &Filter) -> Option<async_graphql::Value> {
let iter = jaq_interpret::RcIter::new(vec![].into_iter());
let mut result = filter.run((
jaq_interpret::Ctx::new(vec![], &iter),
jaq_interpret::Val::from(serde_json::to_value(self.value()?).ok()?),
));
let result = result.next()?;
let result = result.ok()?;
let result = async_graphql::Value::from(result.to_string());
Some(result)
}
}

impl<'a, Ctx: ResolverContextLike<'a>> PathGraphql for EvaluationContext<'a, Ctx> {
Expand Down

1 comment on commit 3369838

@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 7.66ms 3.44ms 100.35ms 72.36%
Req/Sec 3.31k 260.85 3.61k 85.75%

394682 requests in 30.00s, 1.98GB read

Requests/sec: 13154.29

Transfer/sec: 67.52MB

Please sign in to comment.