Skip to content
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

feat: Support mustache on link directives #3176

Merged
merged 18 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock

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

27 changes: 25 additions & 2 deletions src/core/config/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ impl ConfigReader {
config_module: ConfigModule,
parent_dir: Option<&'async_recursion Path>,
) -> anyhow::Result<ConfigModule> {
let reader_ctx = ConfigReaderContext {
runtime: &self.runtime,
vars: &Default::default(),
headers: Default::default(),
};
tusharmath marked this conversation as resolved.
Show resolved Hide resolved

let links: Vec<Link> = config_module
.config()
.links
Expand All @@ -65,7 +71,11 @@ impl ConfigReader {

match link.type_of {
LinkType::Config => {
let source = self.resource_reader.read_file(path).await?;
let source = self
.resource_reader
.read_file(path)
.await?
.render(&reader_ctx);
let content = source.content;
let config = Config::from_source(Source::detect(&source.path)?, &content)?;
config_module = config_module.and_then(|config_module| {
Expand Down Expand Up @@ -184,7 +194,20 @@ impl ConfigReader {
&self,
files: &[T],
) -> anyhow::Result<ConfigModule> {
let files = self.resource_reader.read_files(files).await?;
let reader_ctx = ConfigReaderContext {
runtime: &self.runtime,
vars: &Default::default(),
headers: Default::default(),
};

let files = self
.resource_reader
.read_files(files)
.await?
.into_iter()
.map(|file| file.render(&reader_ctx))
.collect::<Vec<_>>();

let mut config_module = Valid::succeed(ConfigModule::default());

for file in files.iter() {
Expand Down
15 changes: 14 additions & 1 deletion src/core/mustache/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,27 @@ impl Mustache {
PathStringEval::new().eval(self, value)
}

pub fn partial_render(&self, value: &impl PathString) -> String {
self.segments()
.iter()
.map(|segment| match segment {
Segment::Literal(text) => text.to_string(),
Segment::Expression(parts) => {
value.path_string(parts).map(|v| v.to_string()).unwrap_or(
Mustache::from(vec![Segment::Expression(parts.to_vec())]).to_string(),
)
}
})
.collect()
}
tusharmath marked this conversation as resolved.
Show resolved Hide resolved

pub fn render_graphql(&self, value: &impl PathGraphql) -> String {
PathGraphqlEval::new().eval(self, value)
}
}

#[cfg(test)]
mod tests {

mod render {
use std::borrow::Cow;

Expand Down
2 changes: 1 addition & 1 deletion src/core/mustache/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl Display for Mustache {
.iter()
.map(|segment| match segment {
Segment::Literal(text) => text.clone(),
Segment::Expression(parts) => format!("{{{{{}}}}}", parts.join(".")),
Segment::Expression(parts) => format!("{{{{.{}}}}}", parts.join(".")),
})
.collect::<Vec<String>>()
.join("");
Expand Down
16 changes: 8 additions & 8 deletions src/core/mustache/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ mod tests {
#[test]
fn test_to_string() {
let expectations = vec![
r"/users/{{value.id}}/todos",
r"http://localhost:8090/{{foo.bar}}/api/{{hello.world}}/end",
r"http://localhost:{{args.port}}",
r"/users/{{value.userId}}",
r"/bar?id={{args.id}}&flag={{args.flag}}",
r"/foo?id={{value.id}}",
r"{{value.d}}",
r"/posts/{{args.id}}",
r"/users/{{.value.id}}/todos",
r"http://localhost:8090/{{.foo.bar}}/api/{{.hello.world}}/end",
r"http://localhost:{{.args.port}}",
r"/users/{{.value.userId}}",
r"/bar?id={{.args.id}}&flag={{.args.flag}}",
r"/foo?id={{.value.id}}",
r"{{.value.d}}",
r"/posts/{{.args.id}}",
r"http://localhost:8000",
];

Expand Down
10 changes: 10 additions & 0 deletions src/core/resource_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use futures_util::TryFutureExt;
use tailcall_hasher::TailcallHasher;
use url::Url;

use crate::core::config::ConfigReaderContext;
use crate::core::runtime::TargetRuntime;
use crate::core::Mustache;

/// Response of a file read operation
#[derive(Debug)]
Expand All @@ -16,6 +18,14 @@ pub struct FileRead {
pub path: String,
}

impl FileRead {
pub fn render(mut self, reader_context: &ConfigReaderContext) -> Self {
let schema = Mustache::parse(&self.content).partial_render(reader_context);
self.content = schema;
self
}
}

/// Supported Resources by Resource Reader
pub enum Resource {
RawPath(String),
Expand Down
6 changes: 3 additions & 3 deletions tests/core/snapshots/env-value.md_merged.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type Post {
}

type Query {
post1: Post @http(url: "http://jsonplaceholder.typicode.com/posts/{{.env.ID}}")
post2: Post @http(url: "http://jsonplaceholder.typicode.com/posts/{{.env.POST_ID}}")
post3: Post @http(url: "http://jsonplaceholder.typicode.com/posts/{{.env.NESTED_POST_ID}}")
post1: Post @http(url: "http://jsonplaceholder.typicode.com/posts/1")
post2: Post @http(url: "http://jsonplaceholder.typicode.com/posts/2")
post3: Post @http(url: "http://jsonplaceholder.typicode.com/posts/3")
}
22 changes: 22 additions & 0 deletions tests/core/snapshots/test-eval-strict.md_client.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: tests/core/spec.rs
expression: formatted
---
type Post {
foo: String
id: Int!
user: User
userId: Int!
}

type Query {
post(id: Int!): [Post]
}

type User {
id: Int!
}

schema {
query: Query
}
22 changes: 22 additions & 0 deletions tests/core/snapshots/test-eval-strict.md_merged.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: tests/core/spec.rs
expression: formatter
---
schema @server(port: 8080) @upstream(batch: {delay: 100, headers: []}, httpCache: 42) {
query: Query
}

type Post {
foo: String @http(url: "http://jsonplaceholder.typicode.com/posts/foo")
id: Int!
user: User @http(url: "http://jsonplaceholder.typicode.com/users/{{.value.userId}}")
userId: Int!
}

type Query {
post(id: Int!): [Post] @http(url: "http://jsonplaceholder.typicode.com/posts/{{.args.id}}")
}

type User {
id: Int!
}
10 changes: 5 additions & 5 deletions tests/core/snapshots/yaml-nested-unions.md_merged.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ union U1 = T1 | T2 | T3
union U2 = T3 | T4

type Query {
testVar0(u: T1Input!): U @http(url: "http://localhost/users/{{args.u}}")
testVar1(u: T2Input!): U @http(url: "http://localhost/users/{{args.u}}")
testVar2(u: T3Input!): U @http(url: "http://localhost/users/{{args.u}}")
testVar3(u: T4Input!): U @http(url: "http://localhost/users/{{args.u}}")
testVar4(u: T5Input!): U @http(url: "http://localhost/users/{{args.u}}")
testVar0(u: T1Input!): U @http(url: "http://localhost/users/{{.args.u}}")
testVar1(u: T2Input!): U @http(url: "http://localhost/users/{{.args.u}}")
testVar2(u: T3Input!): U @http(url: "http://localhost/users/{{.args.u}}")
testVar3(u: T4Input!): U @http(url: "http://localhost/users/{{.args.u}}")
testVar4(u: T5Input!): U @http(url: "http://localhost/users/{{.args.u}}")
}

type T1 {
Expand Down
18 changes: 9 additions & 9 deletions tests/core/snapshots/yaml-union-in-type.md_merged.snap
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ type NU {
}

type Query {
testVar0Var0(nu: NU__u0!, nnu: NNU__nu0): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar0Var1(nu: NU__u0!, nnu: NNU__nu1): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar0Var2(nu: NU__u0!, nnu: NNU__nu2): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar1Var0(nu: NU__u1!, nnu: NNU__nu0): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar1Var1(nu: NU__u1!, nnu: NNU__nu1): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar1Var2(nu: NU__u1!, nnu: NNU__nu2): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar2Var0(nu: NU__u2!, nnu: NNU__nu0): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar2Var1(nu: NU__u2!, nnu: NNU__nu1): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar2Var2(nu: NU__u2!, nnu: NNU__nu2): U @http(url: "http://localhost/users/{{args.nu.u}}")
testVar0Var0(nu: NU__u0!, nnu: NNU__nu0): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar0Var1(nu: NU__u0!, nnu: NNU__nu1): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar0Var2(nu: NU__u0!, nnu: NNU__nu2): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar1Var0(nu: NU__u1!, nnu: NNU__nu0): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar1Var1(nu: NU__u1!, nnu: NNU__nu1): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar1Var2(nu: NU__u1!, nnu: NNU__nu2): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar2Var0(nu: NU__u2!, nnu: NNU__nu0): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar2Var1(nu: NU__u2!, nnu: NNU__nu1): U @http(url: "http://localhost/users/{{.args.nu.u}}")
testVar2Var2(nu: NU__u2!, nnu: NNU__nu2): U @http(url: "http://localhost/users/{{.args.nu.u}}")
}

type T1 {
Expand Down
6 changes: 3 additions & 3 deletions tests/core/snapshots/yaml-union.md_merged.snap
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ type NU {
}

type Query {
testVar0(u: T1Input!): U @http(url: "http://localhost/users/{{args.u}}/")
testVar1(u: T2Input!): U @http(url: "http://localhost/users/{{args.u}}/")
testVar2(u: T3Input!): U @http(url: "http://localhost/users/{{args.u}}/")
testVar0(u: T1Input!): U @http(url: "http://localhost/users/{{.args.u}}/")
testVar1(u: T2Input!): U @http(url: "http://localhost/users/{{.args.u}}/")
testVar2(u: T3Input!): U @http(url: "http://localhost/users/{{.args.u}}/")
}

type T1 {
Expand Down
20 changes: 15 additions & 5 deletions tests/core/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ use tailcall::core::async_graphql_hyper::{GraphQLBatchRequest, GraphQLRequest};
use tailcall::core::blueprint::{Blueprint, BlueprintError};
use tailcall::core::config::reader::ConfigReader;
use tailcall::core::config::transformer::Required;
use tailcall::core::config::{Config, ConfigModule, Source};
use tailcall::core::config::{Config, ConfigModule, ConfigReaderContext, Source};
use tailcall::core::http::handle_request;
use tailcall::core::print_schema::print_schema;
use tailcall::core::variance::Invariant;
use tailcall::core::Mustache;
use tailcall_prettier::Parser;
use tailcall_valid::{Cause, Valid, ValidationError, Validator};

Expand Down Expand Up @@ -87,7 +88,7 @@ async fn is_sdl_error(spec: &ExecutionSpec, config_module: Valid<ConfigModule, S
false
}

async fn check_identity(spec: &ExecutionSpec) {
async fn check_identity(spec: &ExecutionSpec, reader_ctx: &ConfigReaderContext<'_>) {
// TODO: we should probably figure out a way to do this for every test
// but GraphQL identity checking is very hard, since a lot depends on the code
// style the re-serializing check gives us some of the advantages of the
Expand All @@ -97,7 +98,8 @@ async fn check_identity(spec: &ExecutionSpec) {
if spec.check_identity {
for (source, content) in spec.server.iter() {
if matches!(source, Source::GraphQL) {
let config = Config::from_source(source.to_owned(), content).unwrap();
let content = Mustache::parse(content).partial_render(reader_ctx);
let config = Config::from_source(source.to_owned(), &content).unwrap();
let actual = config.to_sdl();

// \r is added automatically in windows, it's safe to replace it with \n
Expand Down Expand Up @@ -190,11 +192,19 @@ async fn test_spec(spec: ExecutionSpec) {

let mut runtime = runtime::create_runtime(mock_http_client.clone(), spec.env.clone(), None);
runtime.file = Arc::new(File::new(spec.clone()));
let reader_ctx = ConfigReaderContext {
runtime: &runtime.clone(),
vars: &Default::default(),
headers: Default::default(),
};

let reader = ConfigReader::init(runtime);

// Resolve all configs
let config_modules = join_all(spec.server.iter().map(|(source, content)| async {
let config = Config::from_source(source.to_owned(), content)?;
let content = Mustache::parse(content).partial_render(&reader_ctx);

let config = Config::from_source(source.to_owned(), &content)?;

reader.resolve(config, spec.path.parent()).await
}))
Expand Down Expand Up @@ -237,7 +247,7 @@ async fn test_spec(spec: ExecutionSpec) {
.collect::<Result<Vec<_>, _>>()
.unwrap();

check_identity(&spec).await;
check_identity(&spec, &reader_ctx).await;

// client: Check if client spec matches snapshot
if config_modules.len() == 1 {
Expand Down
26 changes: 26 additions & 0 deletions tests/execution/test-eval-strict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
```graphql @config
schema @server(port: 8080) @upstream(httpCache: 42, batch: {delay: 100}) {
query: Query
}

type Query {
post(id: Int!): [Post] @http(url: "http://jsonplaceholder.typicode.com/posts/{{.args.id}}")
}

type User {
id: Int!
}

type Post {
id: Int!
userId: Int!
foo: String @http(url: "http://jsonplaceholder.typicode.com/posts/{{.env.FOO}}")
user: User @http(url: "http://jsonplaceholder.typicode.com/users/{{.value.userId}}")
}
```

```json @env
{
"FOO": "foo"
}
```
Loading