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: Parameter transformation #587

Merged
merged 38 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2374530
update wit
Natoandro Feb 6, 2024
bf75c3a
parameter transform tree
Natoandro Feb 7, 2024
494be63
feat: conversion
Natoandro Feb 9, 2024
7839418
update conversion - add failing test
Natoandro Feb 9, 2024
235f2f3
first working test
Natoandro Feb 9, 2024
f228610
type aware transformer compiler
Natoandro Feb 10, 2024
c3a9377
more tests/fixes
Natoandro Feb 10, 2024
5403bbc
test in-list injection
Natoandro Feb 10, 2024
61d519e
address comments (security)
Natoandro Feb 12, 2024
afc60b5
Merge branch 'main' into feat/apply
Natoandro Feb 12, 2024
d8b869e
inline validators
Natoandro Feb 12, 2024
188f13d
visitor context
Natoandro Feb 12, 2024
293e7ca
inheritance validator
Natoandro Feb 13, 2024
5d4875f
fix validators
Natoandro Feb 14, 2024
47da8bc
test from_parent validation
Natoandro Feb 14, 2024
2a214a0
some doc comments
Natoandro Feb 14, 2024
a798ff1
Merge branch 'main' into feat/apply
Natoandro Feb 14, 2024
b3eb807
TypeScript client SDK for apply
Natoandro Feb 14, 2024
d3734fc
docs: Add doc for parameter transformation
Natoandro Feb 14, 2024
46fb126
upgrade pnpm
Natoandro Feb 14, 2024
78c619c
tests: fix typegraph_core tests
Natoandro Feb 15, 2024
50b1d2a
more tests
Natoandro Feb 15, 2024
85f68d8
fix tests
Natoandro Feb 15, 2024
8211dc2
docs: update docs
Natoandro Feb 15, 2024
6f91dbe
disable docker test on arm64
Natoandro Feb 15, 2024
2070366
fix tests
Natoandro Feb 15, 2024
9a54f26
Merge branch 'main' into feat/apply
Natoandro Feb 15, 2024
ff1c92f
fix visitor
Natoandro Feb 16, 2024
8ed22ef
fix tests
Natoandro Feb 16, 2024
e282c41
fix input validator test
Natoandro Feb 16, 2024
1214cc9
Merge branch 'main' into feat/apply
Natoandro Feb 16, 2024
ddca6e8
update snapshots
Natoandro Feb 16, 2024
85df23a
fix tests
Natoandro Feb 17, 2024
c6b6687
Merge branch 'main' into feat/apply
Natoandro Feb 19, 2024
32ccc87
fix merge confusions
Natoandro Feb 19, 2024
113b81a
fix test
Natoandro Feb 19, 2024
a519f1f
update snapshot
Natoandro Feb 19, 2024
3e4af7d
Merge branch 'main' into feat/apply
Natoandro Feb 19, 2024
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
1 change: 1 addition & 0 deletions libs/common/src/typegraph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Metatype OÜ, licensed under the Elastic License 2.0.
// SPDX-License-Identifier: Elastic-2.0

pub mod parameter_transform;
pub mod runtimes;
pub mod types;
pub mod utils;
Expand Down
60 changes: 60 additions & 0 deletions libs/common/src/typegraph/parameter_transform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright Metatype OÜ, licensed under the Elastic License 2.0.
// SPDX-License-Identifier: Elastic-2.0

use std::collections::HashMap;

#[cfg(feature = "codegen")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[cfg_attr(feature = "codegen", derive(JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "source", rename_all = "lowercase")]
pub enum ParameterTransformLeafNode {
#[serde(rename_all = "camelCase")]
Arg { name: String },
#[serde(rename_all = "camelCase")]
Static { value_json: String },
#[serde(rename_all = "camelCase")]
Secret { key: String },
#[serde(rename_all = "camelCase")]
Context { key: String },
#[serde(rename_all = "camelCase")]
Parent { parent_idx: u32 },
}

#[cfg_attr(feature = "codegen", derive(JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum ParameterTransformParentNode {
#[serde(rename_all = "camelCase")]
Object {
fields: HashMap<String, ParameterTransformNode>,
},
#[serde(rename_all = "camelCase")]
Array { items: Vec<ParameterTransformNode> },
}

#[cfg_attr(feature = "codegen", derive(JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum ParameterTransformNodeData {
Leaf(ParameterTransformLeafNode),
Parent(ParameterTransformParentNode),
}

#[cfg_attr(feature = "codegen", derive(JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ParameterTransformNode {
pub type_idx: u32,
// #[serde(flatten)]
pub data: ParameterTransformNodeData,
}

#[cfg_attr(feature = "codegen", derive(JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FunctionParameterTransform {
pub resolver_input: u32,
pub transform_root: ParameterTransformNode,
}
4 changes: 3 additions & 1 deletion libs/common/src/typegraph/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;

use super::{EffectType, PolicyIndices};
use super::{parameter_transform::FunctionParameterTransform, EffectType, PolicyIndices};

#[cfg_attr(feature = "codegen", derive(JsonSchema))]
#[derive(Serialize, Deserialize, Clone, Debug)]
Expand Down Expand Up @@ -166,6 +166,8 @@ pub struct ListTypeData {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FunctionTypeData {
pub input: u32,
#[serde(rename = "parameterTransform")]
pub parameter_transform: Option<FunctionParameterTransform>,
pub output: u32,
pub materializer: u32,
#[serialize_always]
Expand Down
26 changes: 23 additions & 3 deletions typegate/src/engine/planner/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@ import {
import { mapValues } from "std/collections/map_values.ts";
import { filterValues } from "std/collections/filter_values.ts";

import { EffectType, EitherNode } from "../../typegraph/types.ts";
import {
EffectType,
EitherNode,
FunctionParameterTransform,
} from "../../typegraph/types.ts";

import { getChildTypes, visitTypes } from "../../typegraph/visitor.ts";
import { generateValidator } from "../typecheck/input.ts";
import { getParentId } from "../stage_id.ts";
import { BadContext } from "../../errors.ts";
import { selectInjection } from "./injection_utils.ts";
import {
compileParameterTransformer,
defaultParameterTransformer,
} from "./parameter_transformer.ts";

class MandatoryArgumentError extends Error {
constructor(argDetails: string) {
Expand Down Expand Up @@ -84,6 +92,7 @@ export function collectArgs(
parentProps: Record<string, number>,
typeIdx: TypeIdx,
astNodes: Record<string, ast.ArgumentNode>,
parameterTransform: FunctionParameterTransform | null,
): CollectedArgs {
const collector = new ArgumentCollector(
typegraph,
Expand All @@ -108,6 +117,13 @@ export function collectArgs(
}

const validate = generateValidator(typegraph, typeIdx);
const parameterTransformer = parameterTransform == null
? defaultParameterTransformer
: compileParameterTransformer(
typegraph,
parentProps,
parameterTransform.transform_root,
);

const policies = collector.policies;

Expand All @@ -124,7 +140,10 @@ export function collectArgs(
// typecheck
validate(value);
return {
compute: () => value,
compute: (c) => {
const { parent, context } = c;
return parameterTransformer({ args: value, parent, context });
},
deps: [],
policies,
};
Expand All @@ -136,7 +155,8 @@ export function collectArgs(
compute: (params) => {
const value = mapValues(compute, (c) => c(params));
validate(value);
return value;
const { parent, context } = params;
return parameterTransformer({ args: value, parent, context });
},
deps: Array.from(collector.deps.parent).map((dep) => `${parentId}.${dep}`),
policies,
Expand Down
10 changes: 8 additions & 2 deletions typegate/src/engine/planner/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,13 @@ export class Planner {
}

const schema = this.tg.type(node.typeIdx, Type.FUNCTION);
const { input: inputIdx, output: outputIdx, rate_calls, rate_weight } =
schema;
const {
input: inputIdx,
output: outputIdx,
rate_calls,
rate_weight,
parameterTransform = null,
} = schema;
const outputType = this.tg.type(outputIdx);

const mat = this.tg.materializer(schema.materializer);
Expand Down Expand Up @@ -440,6 +445,7 @@ export class Planner {
parentProps,
inputIdx,
argNodes,
parameterTransform,
);

deps.push(...collected.deps);
Expand Down
161 changes: 161 additions & 0 deletions typegate/src/engine/planner/parameter_transformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright Metatype OÜ, licensed under the Elastic License 2.0.
// SPDX-License-Identifier: Elastic-2.0

import { TypeGraph } from "../../typegraph/mod.ts";
import { ParameterTransformNode } from "../../typegraph/types.ts";

export type TransformParamsInput = {
args: Record<string, any>;
context: Record<string, any>;
parent: Record<string, any>;
};

export function defaultParameterTransformer(input: TransformParamsInput) {
return input.args;
}

export type TransformParams = {
(input: TransformParamsInput): Record<string, any>;
};

export function compileParameterTransformer(
typegraph: TypeGraph,
parentProps: Record<string, number>,
transformerTreeRoot: ParameterTransformNode,
): TransformParams {
const ctx = new TransformerCompilationContext(typegraph, parentProps);
const fnBody = ctx.compile(transformerTreeRoot);
const fn = new Function("input", fnBody) as TransformParams;
return (input) => {
const res = fn(input);
return res;
};
}

class TransformerCompilationContext {
#tg: TypeGraph;
#parentProps: Record<string, number>;
#path: string[] = [];
#latestVarIndex = 0;
#collector: string[] = [];

constructor(typegraph: TypeGraph, parentProps: Record<string, number>) {
this.#tg = typegraph;
this.#parentProps = parentProps;
}

#reset() {
this.#collector = [
"const { args, context, parent } = input;\n",
];
}

compile(rootNode: ParameterTransformNode) {
this.#reset();
const varName = this.#compileNode(rootNode);
this.#collector.push(`return ${varName};`);
return this.#collector.join("\n");
}

#compileNode(node: ParameterTransformNode) {
const { typeIdx, data: nodeData } = node;
if ("type" in nodeData) {
switch (nodeData.type) {
case "object":
return this.#compileObject(typeIdx, nodeData.fields);
case "array":
return this.#compileArray(typeIdx, nodeData.items);
default:
// unreachable
throw new Error(
`Unknown type: ${(nodeData as any).type} at ${this.#path}`,
);
}
} else {
switch (nodeData.source) {
case "arg":
return this.#compileArgInjection(typeIdx, nodeData.name);
case "context":
return this.#compileContextInjection(typeIdx, nodeData.key);
case "secret":
return this.#compileSecretsInjection(typeIdx, nodeData.key);
case "parent":
return this.#compileParentInjection(typeIdx, nodeData.parentIdx);
default:
throw new Error(
`Unknown source: ${nodeData.source} at ${this.#path}`,
);
}
}
}

#compileObject(
_typeIdx: number, // TODO validation
fields: Record<string, ParameterTransformNode>,
): string {
const varName = this.#createVarName();
this.#collector.push(`const ${varName} = {};`);

for (const [key, node] of Object.entries(fields)) {
const path = this.#path;
path.push(key);
const propVarName = this.#compileNode(node);
path.pop();
this.#collector.push(`${varName}["${key}"] = ${propVarName};`);
Natoandro marked this conversation as resolved.
Show resolved Hide resolved
}

return varName;
}

#compileArray(_typeIdx: number, items: ParameterTransformNode[]) {
const varName = this.#createVarName();
this.#collector.push(`const ${varName} = [];`);

for (let i = 0; i < items.length; i++) {
const path = this.#path;
path.push(i.toString());
const propVarName = this.#compileNode(items[i]);
path.pop();
this.#collector.push(`${varName}.push(${propVarName});`);
}

return varName;
}

#compileArgInjection(_typeIdx: number, name: string) {
// TODO type validation ?
const varName = this.#createVarName();
this.#collector.push(`const ${varName} = args["${name}"];`);
return varName;
}

#compileContextInjection(_typeIdx: number, key: string) {
// TODO type validation ?
const varName = this.#createVarName();
this.#collector.push(`const ${varName} = context["${key}"];`);
return varName;
}

// return () => this.tg.parseSecret(typ, secretName);
#compileSecretsInjection(typeIdx: number, key: string) {
const secret = this.#tg.parseSecret(this.#tg.type(typeIdx), key);
// TODO type validation ? -- only additional constraints
const varName = this.#createVarName();
this.#collector.push(`const ${varName} = ${JSON.stringify(secret)};`);
return varName;
}

#compileParentInjection(_typeIdx: number, parentIdx: number) {
// TODO type validation ?
// TODO what if the value is lazy (a function?)
const [key] = Object.entries(this.#parentProps)
.find(([_key, idx]) => idx === parentIdx)!;
const varName = this.#createVarName();
this.#collector.push(`const ${varName} = parent["${key}"];`);
return varName;
}

#createVarName() {
return `var${++this.#latestVarIndex}`;
}
}
37 changes: 37 additions & 0 deletions typegate/src/typegraph/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export type FunctionNode = {
};
as_id: boolean;
input: number;
parameterTransform?: FunctionParameterTransform | null;
output: number;
materializer: number;
rate_weight?: number | null;
Expand Down Expand Up @@ -239,6 +240,34 @@ export type StringFormat =
| "date"
| "date-time"
| "phone";
export type ParameterTransformNodeData =
| ParameterTransformLeafNode
| ParameterTransformParentNode;
export type ParameterTransformLeafNode = {
source: "arg";
name: string;
} | {
source: "static";
valueJson: string;
} | {
source: "secret";
key: string;
} | {
source: "context";
key: string;
} | {
source: "parent";
parentIdx: number;
};
export type ParameterTransformParentNode = {
type: "object";
fields: {
[k: string]: ParameterTransformNode;
};
} | {
type: "array";
items: ParameterTransformNode[];
};
export type EffectType = "create" | "update" | "delete" | "read";
export type TGRuntime = KnownRuntime | UnknownRuntime;
export type KnownRuntime = {
Expand Down Expand Up @@ -362,6 +391,14 @@ export interface SingleValueFor_String {
export interface SingleValueForUint32 {
value: number;
}
export interface FunctionParameterTransform {
resolver_input: number;
transform_root: ParameterTransformNode;
}
export interface ParameterTransformNode {
typeIdx: number;
data: ParameterTransformNodeData;
}
export interface Materializer {
name: string;
runtime: number;
Expand Down
Loading
Loading