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: injection on output types (outjection) #935

Merged
merged 13 commits into from
Dec 9, 2024
25 changes: 25 additions & 0 deletions deno.lock

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

3 changes: 2 additions & 1 deletion import_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"sentry": "npm:@sentry/[email protected]",
"swc": "https://deno.land/x/[email protected]/mod.ts",
"swc/types": "https://esm.sh/@swc/[email protected]/types.d.ts?pin=v135",
"validator": "npm:[email protected]"
"validator": "npm:[email protected]",
"@sinonjs/fake-timers": "npm:@sinonjs/[email protected]"
}
}
3 changes: 3 additions & 0 deletions src/common/src/typegraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub struct Queries {
pub struct TypeMeta {
pub prefix: Option<String>,
pub secrets: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub outjection_secrets: Vec<String>,
pub queries: Queries,
pub cors: Cors,
pub auths: Vec<Auth>,
Expand Down
23 changes: 23 additions & 0 deletions src/common/src/typegraph/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,37 @@ pub enum InjectionNode {
},
}

impl InjectionNode {
pub fn collect_secrets_into(&self, collector: &mut Vec<String>) -> Result<()> {
match self {
InjectionNode::Leaf { injection } => {
if let Injection::Secret(d) = injection {
collector.extend(d.values::<String>()?);
}
}
InjectionNode::Parent { children } => {
for child in children.values() {
child.collect_secrets_into(collector)?;
}
}
}
Ok(())
}
}

#[skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FunctionTypeData<Id = TypeId> {
pub input: Id,
#[serde(rename = "parameterTransform")]
pub parameter_transform: Option<FunctionParameterTransform>,
pub output: Id,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
#[serde(default)]
pub injections: IndexMap<String, InjectionNode>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
#[serde(default)]
pub outjections: IndexMap<String, InjectionNode>,
#[serde(rename = "runtimeConfig")]
pub runtime_config: serde_json::Value,
pub materializer: u32,
Expand Down
20 changes: 20 additions & 0 deletions src/common/src/typegraph/validator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@ impl<'a> TypeVisitor<'a> for Validator {
);
path.pop();
}
// TODO validate outjection
// if !data.outjections.is_empty() {
// let outj_cx = InjectionValidationContext {
// fn_path: current_node.path.to_vec(),
// fn_idx: current_node.type_idx,
// input_idx: data.output,
// parent_object,
// validator: context,
// };
// for (k, outj) in data.outjections.iter() {
// path.push(k.clone());
// self.validate_injection(
// &mut path,
// *parent_object.properties.get(k).unwrap(),
// outj,
// &outj_cx,
// );
// path.pop();
// }
// }
} else if let TypeNode::Either { data, .. } = type_node {
let variants = data.one_of.clone();
for i in 0..variants.len() {
Expand Down
2 changes: 1 addition & 1 deletion src/metagen/src/client_rs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use crate::shared::client::*;
use crate::shared::types::NameMemo;
use crate::shared::types::TypeRenderer;
use crate::utils::GenDestBuf;
use normpath::PathExt;
use utils::normalize_type_title;

#[derive(Serialize, Deserialize, Debug, garde::Validate)]
Expand Down Expand Up @@ -436,6 +435,7 @@ pub fn gen_cargo_toml(crate_name: Option<&str>) -> String {

#[cfg(debug_assertions)]
let dependency = if is_test {
use normpath::PathExt;
let client_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../metagen-client-rs")
.normalize()
Expand Down
1 change: 1 addition & 0 deletions src/metagen/src/fdk_rust/stubs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ mod test {
input: 1,
output: 1,
injections: Default::default(),
outjections: Default::default(),
runtime_config: Default::default(),
rate_calls: false,
rate_weight: None,
Expand Down
1 change: 1 addition & 0 deletions src/metagen/src/tests/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub fn test_typegraph_2() -> Typegraph {
input: 1,
output: 1,
injections: Default::default(),
outjections: Default::default(),
runtime_config: Default::default(),
rate_calls: false,
rate_weight: None,
Expand Down
7 changes: 7 additions & 0 deletions src/typegate/src/engine/injection/dynamic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0.
// SPDX-License-Identifier: MPL-2.0

export default {
"now": () => new Date().toISOString(),
// "uuid": () =>
} as const;
11 changes: 4 additions & 7 deletions src/typegate/src/engine/planner/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
import { QueryFunction as JsonPathQuery } from "../../libs/jsonpath.ts";
import { getInjection } from "../../typegraph/utils.ts";
import { GeneratorNode } from "../../runtimes/random.ts";
import DynamicInjection from "../injection/dynamic.ts";

class MandatoryArgumentError extends Error {
constructor(argDetails: string) {
Expand Down Expand Up @@ -104,7 +105,7 @@ export function collectArgs(
stageId,
effect,
parentProps,
injectionTree,
injectionTree ?? {},
);
const argTypeNode = typegraph.type(typeIdx, Type.OBJECT);
for (const argName of Object.keys(astNodes)) {
Expand Down Expand Up @@ -169,11 +170,6 @@ export function collectArgs(
};
}

const GENERATORS = {
"now": () => new Date().toISOString(),
// "uuid": () =>
} as const;

interface Dependencies {
context: Set<string>;
parent: Set<string>;
Expand Down Expand Up @@ -814,7 +810,8 @@ class ArgumentCollector {
if (generatorName == null) {
return null;
}
const generator = GENERATORS[generatorName as keyof typeof GENERATORS];
const generator =
DynamicInjection[generatorName as keyof typeof DynamicInjection];
if (generator == null) {
throw new Error(
`Unknown generator '${generatorName}' for dynamic injection`,
Expand Down
10 changes: 10 additions & 0 deletions src/typegate/src/engine/planner/injection_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@
}
return null;
}

export function getInjectionValues<T = string>(
data: InjectionData,

Check warning on line 21 in src/typegate/src/engine/planner/injection_utils.ts

View check run for this annotation

Codecov / codecov/patch

src/typegate/src/engine/planner/injection_utils.ts#L20-L21

Added lines #L20 - L21 were not covered by tests
): T[] {
if ("value" in data) {
return [data.value as T];
}

Check warning on line 25 in src/typegate/src/engine/planner/injection_utils.ts

View check run for this annotation

Codecov / codecov/patch

src/typegate/src/engine/planner/injection_utils.ts#L23-L25

Added lines #L23 - L25 were not covered by tests

return Object.values(data).filter((v) => typeof v === "string") as T[];
}

Check warning on line 28 in src/typegate/src/engine/planner/injection_utils.ts

View check run for this annotation

Codecov / codecov/patch

src/typegate/src/engine/planner/injection_utils.ts#L27-L28

Added lines #L27 - L28 were not covered by tests
64 changes: 59 additions & 5 deletions src/typegate/src/engine/planner/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,19 @@ import { getLogger } from "../../log.ts";
import { generateVariantMatcher } from "../typecheck/matching_variant.ts";
import { mapValues } from "@std/collections/map-values";
import { DependencyResolver } from "./dependency_resolver.ts";
import { Runtime } from "../../runtimes/Runtime.ts";
import { getInjection } from "../../typegraph/utils.ts";
import { Injection } from "../../typegraph/types.ts";
import { getInjectionValues } from "./injection_utils.ts";

const logger = getLogger(import.meta);

interface Scope {
runtime: Runtime;
fnIdx: number;
path: string[];
}

interface Node {
name: string;
path: string[];
Expand All @@ -37,6 +47,7 @@ interface Node {
typeIdx: number;
parent?: Node;
parentStage?: ComputeStage;
scope?: Scope;
}

export interface Plan {
Expand Down Expand Up @@ -281,6 +292,19 @@ export class Planner {
) {
throw this.unexpectedFieldError(node, name);
}
const fieldType = fieldIdx == null ? null : this.tg.type(fieldIdx);
const scope: Scope | undefined =
(fieldType && fieldType.type === Type.FUNCTION)
? {
runtime: this.tg
.runtimeReferences[
this.tg.materializer(fieldType.materializer).runtime
],
fnIdx: fieldIdx,
path: [],
}
: node.scope && { ...node.scope, path: [...node.scope.path, name] };

return {
parent: node,
name,
Expand All @@ -289,9 +313,17 @@ export class Planner {
args: args ?? [],
typeIdx: props[name],
parentStage,
scope,
};
}

#getOutjection(scope: Scope): Injection | null {
const outjectionTree =
this.tg.type(scope.fnIdx, Type.FUNCTION).outjections ??
{};
return getInjection(outjectionTree, scope.path);
}

/**
* Create compute stages for `node` and its child nodes.
* @param field {FieldNode} The selection field for node
Expand Down Expand Up @@ -374,13 +406,38 @@ export class Planner {
return stages;
}

#createOutjectionStage(node: Node, outjection: Injection): ComputeStage {
return this.createComputeStage(node, {
// TODO parent if from parent
dependencies: [],
args: null,
effect: null,
runtime: this.tg.runtimeReferences[this.tg.denoRuntimeIdx],
batcher: this.tg.nextBatcher(this.tg.type(node.typeIdx)),
rateCalls: true,
rateWeight: 0,
materializer: {
runtime: this.tg.denoRuntimeIdx,
name: "outjection",
data: outjection,
effect: { effect: null, idempotent: true },
},
});
}

/**
* Create `ComputeStage`s for `node` and its child nodes,
* where `node` corresponds to a selection field for a value (non-function type).
* @param node
* @param policies
*/
private traverseValueField(node: Node): ComputeStage[] {
const outjection = node.scope && this.#getOutjection(node.scope!);
if (outjection) {
return [
this.#createOutjectionStage(node, outjection),
];
}
const stages: ComputeStage[] = [];
const schema = this.tg.type(node.typeIdx);

Expand All @@ -392,11 +449,8 @@ export class Planner {
);
}

const runtime = (schema.type === Type.FUNCTION)
? this.tg
.runtimeReferences[(this.tg.materializer(schema.materializer)).runtime]
: node.parentStage?.props.runtime ??
this.tg.runtimeReferences[this.tg.denoRuntimeIdx];
const runtime = node.scope?.runtime ??
this.tg.runtimeReferences[this.tg.denoRuntimeIdx];

const stage = this.createComputeStage(node, {
dependencies: node.parentStage ? [node.parentStage.id()] : [],
Expand Down
5 changes: 5 additions & 0 deletions src/typegate/src/engine/typecheck/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import {
type Validator,
type ValidatorFn,
} from "./common.ts";
import { getLogger } from "../../log.ts";

const logger = getLogger(import.meta);

export function generateValidator(
tg: TypeGraph,
Expand All @@ -42,6 +45,8 @@ export function generateValidator(
const messages = errors
.map(([path, msg]) => ` - at ${path}: ${msg}\n`)
.join("");
logger.error("Validation failed: value={}", value);
logger.error("Validation errors:\n{}", messages);
throw new Error(`Validation errors:\n${messages}`);
}
};
Expand Down
Loading
Loading