diff --git a/libs/common/src/typegraph/mod.rs b/libs/common/src/typegraph/mod.rs index 29925a236a..18acd14eab 100644 --- a/libs/common/src/typegraph/mod.rs +++ b/libs/common/src/typegraph/mod.rs @@ -98,6 +98,7 @@ pub struct TypeMeta { pub auths: Vec, pub rate: Option, pub version: String, + pub random_seed: Option, } #[cfg_attr(feature = "codegen", derive(JsonSchema))] diff --git a/libs/common/src/typegraph/types.rs b/libs/common/src/typegraph/types.rs index 575df5867f..99d3c87af4 100644 --- a/libs/common/src/typegraph/types.rs +++ b/libs/common/src/typegraph/types.rs @@ -50,6 +50,7 @@ pub enum Injection { Secret(InjectionData), Parent(InjectionData), Dynamic(InjectionData), + Random(InjectionData), } #[cfg_attr(feature = "codegen", derive(JsonSchema))] @@ -116,7 +117,6 @@ pub enum StringFormat { Ean, Date, DateTime, - // Path, Phone, } diff --git a/typegate/deno.lock b/typegate/deno.lock index 2a87c2194a..f7d7b0d2f0 100644 --- a/typegate/deno.lock +++ b/typegate/deno.lock @@ -1106,5 +1106,13 @@ "https://raw.githubusercontent.com/metatypedev/metatype/feat/MET-250/refactor-ffi/typegate/src/typegraph/visitor.ts": "854f2dd1adadc62ea2050f6e0f293c88f76d4feefb7620bcc490049fb8967043", "https://raw.githubusercontent.com/metatypedev/metatype/feat/MET-250/refactor-ffi/typegate/src/types.ts": "1857e6bf96b0642e15352e10dd4e175c4983edc421868ae0158ce271e075926d", "https://raw.githubusercontent.com/metatypedev/metatype/feat/MET-250/refactor-ffi/typegate/src/utils.ts": "8a34944dc326d1759c67fcdd4a714f99838d5eac040773bcdb7287a00118923b" + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:chance@^1.1.11", + "npm:yarn@^1.22.19" + ] + } } } diff --git a/typegate/src/engine/planner/args.ts b/typegate/src/engine/planner/args.ts index 8604837692..bbd8651cfd 100644 --- a/typegate/src/engine/planner/args.ts +++ b/typegate/src/engine/planner/args.ts @@ -245,7 +245,7 @@ class ArgumentCollector { private collectArgImpl(node: CollectNode): ComputeArg { const { astNode, typeIdx } = node; - const typ = this.tg.type(typeIdx); + const typ: TypeNode = this.tg.type(typeIdx); this.addPoliciesFrom(typeIdx); @@ -695,6 +695,10 @@ class ArgumentCollector { } return generator; } + + case "random": { + return () => this.tg.getRandom(typ); + } } } diff --git a/typegate/src/runtimes/random.ts b/typegate/src/runtimes/random.ts index 15472a466f..31f1d1f3e7 100644 --- a/typegate/src/runtimes/random.ts +++ b/typegate/src/runtimes/random.ts @@ -78,48 +78,106 @@ export class RandomRuntime extends Runtime { execute(typ: TypeNode): Resolver { return () => { - return this.randomizeRecursively(typ); + return randomizeRecursively(typ, this.chance, this._tgTypes); }; } +} - randomizeRecursively(typ: TypeNode): any { - const config = typ.config ?? {}; - if (Object.prototype.hasOwnProperty.call(config, "gen")) { - const { gen, ...arg } = config; - return this.chance[gen as string](arg); +export default function randomizeRecursively( + typ: TypeNode, + chance: typeof Chance, + tgTypes: TypeNode[], +): any { + const config = typ.config ?? {}; + if (Object.prototype.hasOwnProperty.call(config, "gen")) { + const { gen, ...arg } = config; + return chance[gen as string](arg); + } + switch (typ.type) { + case "object": + return {}; + case "optional": { + const childNodeName = tgTypes[typ.item]; + return chance.bool() + ? randomizeRecursively(childNodeName, chance, tgTypes) + : null; } - switch (typ.type) { - case "object": - return {}; - case "optional": { - const childNodeName = this.getTgTypeNameByIndex(typ.item); - return this.chance.bool() - ? this.randomizeRecursively(childNodeName) - : null; + case "integer": + return chance.integer(); + case "string": + if (typ.format === "uuid") { + return chance.guid(); + } + if (typ.format === "email") { + return chance.email(); + } + if (typ.format === "uri") { + return chance.url(); + } + if (typ.format === "hostname") { + return chance.domain(); + } + if (typ.format === "date-time") { + const randomDate = chance.date(); + + // Get the timestamp of the random date + const timestamp = randomDate.getTime(); + console.log(randomDate); + + // Create a new Date object with the timestamp adjusted for the local timezone offset + const dateInUtc = new Date( + timestamp - randomDate.getTimezoneOffset() * 60000, + ); + return dateInUtc.toISOString(); + } + if (typ.format === "phone") { + return chance.phone(); } - case "integer": - return this.chance.integer(); - case "string": - if (typ.format === "uuid") { - return this.chance.guid(); - } - if (typ.format === "email") { - return this.chance.email(); - } - return this.chance.string(); - case "boolean": - return this.chance.bool(); - case "list": { - const res = []; - let size = this.chance.integer({ min: 1, max: 10 }); - const childNodeName = this.getTgTypeNameByIndex(typ.items); - while (size--) { - res.push(this.randomizeRecursively(childNodeName)); - } - return res; + if (typ.format == "ean") { + return generateEAN(chance); } - default: - throw new Error(`type not supported "${typ.type}"`); + return chance.string(); + case "boolean": + return chance.bool(); + case "list": { + const res = []; + let size = chance.integer({ min: 1, max: 10 }); + const childNodeName = tgTypes[typ.items]; + while (size--) { + res.push( + randomizeRecursively(childNodeName, chance, tgTypes), + ); + } + return res; } + + default: + throw new Error(`type not supported "${typ.type}"`); } } + +function generateEAN(chance: typeof Chance) { + let ean = "0"; + + for (let i = 1; i <= 11; i++) { + ean += chance.integer({ min: 0, max: 9 }).toString(); + } + + const checkDigit = calculateCheckDigit(ean); + ean += checkDigit; + + return ean; +} + +function calculateCheckDigit(ean: string) { + const digits = ean.split("").map(Number); + + let sum = 0; + for (let i = 0; i < digits.length; i++) { + sum += (i % 2 === 0) ? digits[i] : digits[i] * 3; + } + + const checkDigit = (10 - (sum % 10)) % 10; + + return checkDigit.toString(); +} diff --git a/typegate/src/typegraph/mod.ts b/typegate/src/typegraph/mod.ts index 0ef672739c..e9c850da8a 100644 --- a/typegate/src/typegraph/mod.ts +++ b/typegate/src/typegraph/mod.ts @@ -7,6 +7,7 @@ import type { DenoRuntime } from "../runtimes/deno/deno.ts"; import { Runtime } from "../runtimes/Runtime.ts"; import { ensure, ensureNonNullable } from "../utils.ts"; import { typegraph_validate } from "native"; +import Chance from "chance"; import { initAuth, @@ -41,6 +42,7 @@ import type { import { InternalAuth } from "../services/auth/protocols/internal.ts"; import { Protocol } from "../services/auth/protocols/protocol.ts"; import { initRuntime } from "../runtimes/mod.ts"; +import randomizeRecursively from "../runtimes/random.ts"; export { Cors, Rate, TypeGraphDS, TypeMaterializer, TypePolicy, TypeRuntime }; @@ -349,6 +351,30 @@ export class TypeGraph { ); } + getRandom( + schema: TypeNode, + ): number | string | null { + const tgTypes: TypeNode[] = this.tg.types; + let seed = 12; // default seed + if ( + this.tg.meta.random_seed !== undefined && + this.tg.meta.random_seed !== null + ) { + seed = this.tg.meta.random_seed; + } + const chance: typeof Chance = new Chance(seed); + + try { + const result = randomizeRecursively(schema, chance, tgTypes); + + return result; + } catch (_) { + throw new Error( + `invalid type for random injection: ${schema.type}`, + ); + } + } + nextBatcher = ( type: TypeNode, ): Batcher => { diff --git a/typegate/src/typegraph/types.ts b/typegate/src/typegraph/types.ts index 9ba5fc7fc5..59008e64ae 100644 --- a/typegate/src/typegraph/types.ts +++ b/typegate/src/typegraph/types.ts @@ -223,6 +223,9 @@ export type Injection = { } | { source: "dynamic"; data: InjectionDataFor_String; +} | { + source: "random"; + data: InjectionDataFor_String; }; export type InjectionDataFor_String = SingleValueFor_String | { [k: string]: string; @@ -503,6 +506,7 @@ export interface TypeMeta { auths: Auth[]; rate?: Rate | null; version: string; + random_seed: number | undefined; } export interface Queries { dynamic: boolean; diff --git a/typegate/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap b/typegate/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap index c7b769c6ba..f27cb41c0b 100644 --- a/typegate/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap +++ b/typegate/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap @@ -326,7 +326,8 @@ snapshot[`typegraphs creation 1`] = ` "context_identifier": "user", "local_excess": 5 }, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]' @@ -533,7 +534,8 @@ snapshot[`typegraphs creation 2`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]\` @@ -815,7 +817,8 @@ snapshot[`typegraphs creation 3`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]\` @@ -1147,7 +1150,8 @@ snapshot[`typegraphs creation 4`] = ` "context_identifier": "user", "local_excess": 5 }, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]' @@ -1354,7 +1358,8 @@ snapshot[`typegraphs creation 5`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]\` @@ -1636,7 +1641,8 @@ snapshot[`typegraphs creation 6`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]\` diff --git a/typegate/tests/random/injection/random_injection.py b/typegate/tests/random/injection/random_injection.py new file mode 100644 index 0000000000..81ce92bed1 --- /dev/null +++ b/typegate/tests/random/injection/random_injection.py @@ -0,0 +1,48 @@ +from typegraph.policy import Policy +from typegraph.runtimes.deno import DenoRuntime + +from typegraph import Graph, t, typegraph + + +@typegraph() +def random_injection(g: Graph): + pub = Policy.public() + deno = DenoRuntime() + + user = t.struct( + { + "id": t.uuid().from_random(), + "ean": t.ean().from_random(), + "name": t.string(config={"gen": "name"}).from_random(), + "age": t.integer(config={"gen": "age", "type": "adult"}).from_random(), + "married": t.boolean().from_random(), + "birthday": t.datetime().from_random(), + "friends": t.list(t.string(config={"gen": "first"})).from_random(), + "phone": t.string(config={"gen": "phone"}).from_random(), + "gender": t.string(config={"gen": "gender"}).from_random(), + "firstname": t.string(config={"gen": "first"}).from_random(), + "lastname": t.string(config={"gen": "last"}).from_random(), + "occupation": t.string(config={"gen": "profession"}).from_random(), + "street": t.string(config={"gen": "address"}).from_random(), + "city": t.string(config={"gen": "city"}).from_random(), + "postcode": t.string(config={"gen": "postcode"}).from_random(), + "country": t.string( + config={"gen": "country", "full": "true"} + ).from_random(), + "uri": t.uri().from_random(), + "hostname": t.string(format="hostname").from_random(), + } + ) + + random_list = t.struct( + { + "names": t.list(t.string(config={"gen": "name"})).from_random(), + } + ) + # Configure random injection seed value or the default will be used + g.configure_random_injection(seed=1) + g.expose( + pub, + randomUser=deno.identity(user), + randomList=deno.identity(random_list), + ) diff --git a/typegate/tests/random/injection/random_injection.ts b/typegate/tests/random/injection/random_injection.ts new file mode 100644 index 0000000000..8436a7a75b --- /dev/null +++ b/typegate/tests/random/injection/random_injection.ts @@ -0,0 +1,40 @@ +// Copyright Metatype OÜ, licensed under the Elastic License 2.0. +// SPDX-License-Identifier: Elastic-2.0 + +import { Policy, t, typegraph } from "@typegraph/sdk/index.js"; +import { DenoRuntime } from "@typegraph/sdk/runtimes/deno.js"; + +const user = t.struct({ + id: t.uuid().fromRandom(), + ean: t.ean().fromRandom(), + name: t.string({}, { config: { gen: "name" } }).fromRandom(), + age: t.integer({}, { config: { gen: "age", type: "adult" } }).fromRandom(), + married: t.boolean().fromRandom(), + email: t.string({ format: "email" }).fromRandom(), + birthday: t.datetime().fromRandom(), + friends: t.list(t.string({}, { config: { gen: "name" } })).fromRandom(), + phone: t.string({}, { config: { gen: "phone" } }).fromRandom(), + gender: t.string({}, { config: { gen: "gender" } }).fromRandom(), + firstname: t.string({}, { config: { gen: "first" } }).fromRandom(), + lastname: t.string({}, { config: { gen: "last" } }).fromRandom(), + occupation: t.string({}, { config: { gen: "profession" } }).fromRandom(), + street: t.string({}, { config: { gen: "address" } }).fromRandom(), + city: t.string({}, { config: { gen: "city" } }).fromRandom(), + postcode: t.string({}, { config: { gen: "postcode" } }).fromRandom(), + country: t.string({}, { config: { gen: "country", full: true } }) + .fromRandom(), + uri: t.string({ format: "uri" }).fromRandom(), + hostname: t.string({ format: "hostname" }).fromRandom(), +}); + +typegraph("random_injection", (g: any) => { + const pub = Policy.public(); + const deno = new DenoRuntime(); + + // Configure random injection seed value or the default will be used + g.configureRandomInjection({ seed: 1 }); + + g.expose({ + randomUser: deno.identity(user).withPolicy(pub), + }); +}); diff --git a/typegate/tests/random/injection/random_injection_test.ts b/typegate/tests/random/injection/random_injection_test.ts new file mode 100644 index 0000000000..0b18d83462 --- /dev/null +++ b/typegate/tests/random/injection/random_injection_test.ts @@ -0,0 +1,138 @@ +// Copyright Metatype OÜ, licensed under the Elastic License 2.0. +// SPDX-License-Identifier: Elastic-2.0 + +import { gql, Meta } from "../../utils/mod.ts"; + +Meta.test("Python: Random Injection", async (metaTest) => { + const engine = await metaTest.engine("random/injection/random_injection.py"); + + await metaTest.should("generate random values", async () => { + await gql` + query { + randomUser { + id + ean + name + age + married + birthday + phone + gender + firstname + lastname + friends + occupation + street + city + postcode + country + uri + hostname + } + } + ` + .expectData({ + randomUser: { + id: "1069ace0-cdb1-5c1f-8193-81f53d29da35", + ean: "0497901391205", + name: "Landon Glover", + age: 38, + married: true, + birthday: "2124-06-22T22:00:07.302Z", + phone: "(587) 901-3720", + gender: "Male", + firstname: "Landon", + lastname: "Mengoni", + friends: ["Hettie", "Mary", "Sean", "Ethel", "Joshua"], + occupation: "Health Care Manager", + street: "837 Wubju Drive", + city: "Urbahfec", + postcode: "IM9 9AD", + country: "Indonesia", + uri: "http://wubju.bs/ma", + hostname: "wubju.bs", + }, + }) + .on(engine); + }); + + await metaTest.should("work on random lists", async () => { + await gql` + query { + randomList { + names + } + } + `.expectData({ + randomList: { + names: [ + "Hettie Huff", + "Nicholas Mills", + "Ethel Marshall", + "Phillip Gonzales", + "Russell Barber", + ], + }, + }).on(engine); + }); +}); + +Meta.test("Deno: Random Injection", async (metaTest) => { + const engine = await metaTest.engine("random/injection/random_injection.ts"); + + await metaTest.should("work", async () => { + await gql` + query { + randomUser { + id + ean + name + age + married + birthday + phone + gender + firstname + lastname + friends + occupation + street + city + postcode + country + uri + hostname + } + } + ` + .expectData({ + randomUser: { + id: "1069ace0-cdb1-5c1f-8193-81f53d29da35", + ean: "0497901391205", + name: "Landon Glover", + age: 38, + married: true, + birthday: "2124-06-22T22:00:07.302Z", + phone: "(587) 901-3720", + gender: "Male", + firstname: "Landon", + lastname: "Mengoni", + friends: [ + "Hettie Huff", + "Nicholas Mills", + "Ethel Marshall", + "Phillip Gonzales", + "Russell Barber", + ], + occupation: "Health Care Manager", + street: "837 Wubju Drive", + city: "Urbahfec", + postcode: "IM9 9AD", + country: "Indonesia", + uri: "http://wubju.bs/ma", + hostname: "wubju.bs", + }, + }) + .on(engine); + }); +}); diff --git a/typegate/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap b/typegate/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap index 999cfcf05e..85f5d84336 100644 --- a/typegate/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap +++ b/typegate/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap @@ -184,7 +184,8 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } }' `; diff --git a/typegate/tests/runtimes/s3/__snapshots__/s3_test.ts.snap b/typegate/tests/runtimes/s3/__snapshots__/s3_test.ts.snap index cffef45f86..d9bb59185b 100644 --- a/typegate/tests/runtimes/s3/__snapshots__/s3_test.ts.snap +++ b/typegate/tests/runtimes/s3/__snapshots__/s3_test.ts.snap @@ -486,7 +486,8 @@ snapshot[`s3 typegraphs 1`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } }' `; diff --git a/typegate/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap b/typegate/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap index c5999b20d2..3afa661e1e 100644 --- a/typegate/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap +++ b/typegate/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap @@ -375,7 +375,8 @@ snapshot[`Typegraph using temporal 1`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]' @@ -756,7 +757,8 @@ snapshot[`Typegraph using temporal 2`] = ` }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } ]' diff --git a/typegate/tests/utils/bindings_test.ts b/typegate/tests/utils/bindings_test.ts index 2d0d77b454..e2c185c93d 100644 --- a/typegate/tests/utils/bindings_test.ts +++ b/typegate/tests/utils/bindings_test.ts @@ -138,6 +138,7 @@ Deno.test("typegraphValidate", () => { "auths": [], "rate": null, "version": "0.0.3", + "random_seed": null, }, }; const str = JSON.stringify(json); diff --git a/typegraph/core/src/global_store.rs b/typegraph/core/src/global_store.rs index bc13a818bf..8de159fb76 100644 --- a/typegraph/core/src/global_store.rs +++ b/typegraph/core/src/global_store.rs @@ -61,6 +61,7 @@ pub struct Store { auths: Vec, deploy_cwd_dir: Option, + random_seed: Option, latest_alias_no: u32, } @@ -223,6 +224,14 @@ impl Store { with_store(|s| s.deploy_cwd_dir.clone()) } + pub fn get_random_seed() -> Option { + with_store(|store| store.random_seed) + } + + pub fn set_random_seed(value: Option) { + with_store_mut(|store| store.random_seed = value) + } + pub fn pick_branch_by_path(supertype_id: TypeId, path: &[String]) -> Result<(Type, TypeId)> { let (_, supertype) = supertype_id.resolve_ref()?; let supertype = &supertype; diff --git a/typegraph/core/src/lib.rs b/typegraph/core/src/lib.rs index e596070e0b..9008cec7a1 100644 --- a/typegraph/core/src/lib.rs +++ b/typegraph/core/src/lib.rs @@ -535,6 +535,10 @@ impl wit::core::Guest for Lib { default_policy, ) } + + fn set_seed(seed: Option) -> Result<()> { + typegraph::set_seed(seed) + } } #[macro_export] diff --git a/typegraph/core/src/runtimes/prisma/model.rs b/typegraph/core/src/runtimes/prisma/model.rs index ae8a14f629..e9b7a5a8e6 100644 --- a/typegraph/core/src/runtimes/prisma/model.rs +++ b/typegraph/core/src/runtimes/prisma/model.rs @@ -296,7 +296,7 @@ impl TryFrom<&common::typegraph::Injection> for Injection { use common::typegraph::Injection as I; match injection { - I::Static(inj) | I::Secret(inj) | I::Context(inj) => { + I::Static(inj) | I::Secret(inj) | I::Context(inj) | I::Random(inj) => { Self::convert_injection(inj).ok_or(()) } I::Parent(inj) => Self::convert_injection(inj).ok_or(()), diff --git a/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap b/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap index 17e27ee128..aedd825aea 100644 --- a/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap +++ b/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap @@ -133,6 +133,7 @@ expression: typegraph }, "auths": [], "rate": null, - "version": "0.0.3" + "version": "0.0.3", + "random_seed": null } } diff --git a/typegraph/core/src/typegraph.rs b/typegraph/core/src/typegraph.rs index bdfcbb971c..87886b53e0 100644 --- a/typegraph/core/src/typegraph.rs +++ b/typegraph/core/src/typegraph.rs @@ -109,6 +109,7 @@ pub fn init(params: TypegraphInitParams) -> Result<()> { prefix: params.prefix, rate: params.rate.map(|v| v.into()), secrets: vec![], + random_seed: Default::default(), }, types: vec![], saved_store_state: Some(Store::save()), @@ -210,6 +211,7 @@ pub fn finalize(mode: TypegraphFinalizeMode) -> Result { dynamic: ctx.meta.queries.dynamic, endpoints: Store::get_graphql_endpoints(), }, + random_seed: Store::get_random_seed(), auths, ..ctx.meta }, @@ -307,6 +309,11 @@ pub fn expose( })? } +pub fn set_seed(seed: Option) -> Result<()> { + Store::set_random_seed(seed); + Ok(()) +} + impl TypegraphContext { pub fn register_type( &mut self, diff --git a/typegraph/core/wit/typegraph.wit b/typegraph/core/wit/typegraph.wit index 1ffc099015..ff62c9228a 100644 --- a/typegraph/core/wit/typegraph.wit +++ b/typegraph/core/wit/typegraph.wit @@ -215,6 +215,8 @@ interface core { rename-type: func(tpe: type-id, new-name: string) -> result expose: func(fns: list>, default-policy: option>) -> result<_, error> + + set-seed: func(seed: option) -> result<_, error> type runtime-id = u32 type materializer-id = u32 diff --git a/typegraph/node/sdk/src/typegraph.ts b/typegraph/node/sdk/src/typegraph.ts index e188821beb..094c27dde6 100644 --- a/typegraph/node/sdk/src/typegraph.ts +++ b/typegraph/node/sdk/src/typegraph.ts @@ -66,6 +66,7 @@ export interface TypegraphBuilderArgs extends InjectionSourceType { rest: (graphql: string) => number; auth: (value: Auth | RawAuth) => number; ref: (name: string) => t.Typedef; + configureRandomInjection: (params: {seed: number}) => void; } export class InheritDef { @@ -94,6 +95,11 @@ export class InheritDef { this.payload = serializeFromParentInjection(value); return this; } + + fromRandom() { + this.payload = serializeGenericInjection("random", null); + return this; + } } export type TypegraphBuilder = (g: TypegraphBuilderArgs) => void; @@ -195,6 +201,9 @@ export function typegraph( ref: (name: string) => { return genRef(name); }, + configureRandomInjection: (params: {seed: number}) => { + return core.setSeed(params.seed); + }, ...InjectionSource, }; diff --git a/typegraph/node/sdk/src/types.ts b/typegraph/node/sdk/src/types.ts index 6517fe77f3..4983d38c7a 100644 --- a/typegraph/node/sdk/src/types.ts +++ b/typegraph/node/sdk/src/types.ts @@ -173,6 +173,12 @@ export class Typedef { serializeFromParentInjection(value), ); } + + fromRandom() { + return this.withInjection( + serializeGenericInjection("random", null), + ); + } } class Boolean extends Typedef { @@ -325,6 +331,18 @@ export function datetime() { return string({ format: "date-time" }); } +export function json() { + return string({ format: "json" }); +} + +export function hostname() { + return string({ format: "hostname" }); +} + +export function phone() { + return string({ format: "phone" }); +} + // Note: enum is a reserved word export function enum_(variants: string[], base: SimplifiedBase = {}) { return string({ diff --git a/typegraph/node/sdk/src/utils/injection_utils.ts b/typegraph/node/sdk/src/utils/injection_utils.ts index 5b45ac8241..d6463386d9 100644 --- a/typegraph/node/sdk/src/utils/injection_utils.ts +++ b/typegraph/node/sdk/src/utils/injection_utils.ts @@ -42,6 +42,9 @@ export function serializeInjection( } } + if (value === null) { + return JSON.stringify({ source, data: {} }); + } return JSON.stringify({ source, data: { value: valueMapper(value) }, @@ -52,7 +55,7 @@ export function serializeGenericInjection( source: InjectionSource, value: InjectionValue, ) { - const allowed: InjectionSource[] = ["dynamic", "context", "secret"]; + const allowed: InjectionSource[] = ["dynamic", "context", "secret", "random"]; if (allowed.includes(source)) { return serializeInjection(source, value); } diff --git a/typegraph/node/sdk/src/utils/type_utils.ts b/typegraph/node/sdk/src/utils/type_utils.ts index 9f23750447..3e4b5e7b64 100644 --- a/typegraph/node/sdk/src/utils/type_utils.ts +++ b/typegraph/node/sdk/src/utils/type_utils.ts @@ -23,5 +23,6 @@ export type InjectionSource = | "static" | "context" | "parent" - | "secret"; + | "secret" + | "random"; export type InjectionValue = T | PerEffect; diff --git a/typegraph/python/typegraph/graph/typegraph.py b/typegraph/python/typegraph/graph/typegraph.py index d82594e797..828583634f 100644 --- a/typegraph/python/typegraph/graph/typegraph.py +++ b/typegraph/python/typegraph/graph/typegraph.py @@ -149,6 +149,11 @@ def auth(self, value: Union[Auth, RawAuth]): def ref(self, name: str) -> "t.typedef": return gen_ref(name) + def configure_random_injection(self, seed: int): + res = core.set_seed(store, seed) + if isinstance(res, Err): + raise Exception(res.value) + def as_arg(self, name: Optional[str] = None): return ApplyFromArg(name) diff --git a/typegraph/python/typegraph/injection.py b/typegraph/python/typegraph/injection.py index e500fd759d..e474fce9b8 100644 --- a/typegraph/python/typegraph/injection.py +++ b/typegraph/python/typegraph/injection.py @@ -3,6 +3,7 @@ import json from typing import Callable, Dict, Union + from typegraph.effects import EffectType from typegraph.graph.typegraph import gen_ref @@ -22,6 +23,9 @@ def serialize_injection( } return json.dumps({"source": source, "data": value_per_effect}) + if value is None: + return json.dumps({"source": source, "data": {}}) + return json.dumps({"source": source, "data": {"value": value_mapper(value)}}) @@ -32,7 +36,7 @@ def serialize_static_injection(value: Union[any, Dict[EffectType, any]]): def serialize_generic_injection(source: str, value: Union[any, Dict[EffectType, any]]): - allowed = ["dynamic", "context", "secret"] + allowed = ["dynamic", "context", "secret", "random"] if source in allowed: return serialize_injection(source, value=value) raise Exception(f"source must be one of ${', '.join(allowed)}") @@ -85,3 +89,7 @@ def from_secret(self, value: Union[str, Dict[EffectType, str]]): def from_parent(self, value: Union[str, Dict[EffectType, str]]): self.payload = serialize_parent_injection(value) return self + + def from_random(self): + self.payload = serialize_generic_injection("random", None) + return self diff --git a/typegraph/python/typegraph/t.py b/typegraph/python/typegraph/t.py index c8ed9e29ae..51441fab6b 100644 --- a/typegraph/python/typegraph/t.py +++ b/typegraph/python/typegraph/t.py @@ -130,6 +130,9 @@ def from_secret(self, value: Union[str, Dict[EffectType, str]]): def from_parent(self, value: Union[str, Dict[EffectType, str]]): return self._with_injection(serialize_parent_injection(value)) + def from_random(self): + return self._with_injection(serialize_generic_injection("random", None)) + class _TypeWithPolicy(typedef): base: "typedef"