Skip to content

Commit bbf0b95

Browse files
authored
feat(sdk): from_random injection (#593)
<!-- Pull requests are squash merged using: - their title as the commit message - their description as the commit body Having a good title and description is important for the users to get readable changelog and understand when they need to update his code and how. --> <!-- Explain WHAT the change is --> This change includes changes in StringFormats(added some string formats), logic to provide random values for type nodes and tests to validate the changes. The changes are mostly in the typegraph sdk. #### Motivation and context <!-- Explain WHY the was made or link an issue number --> This feature enables the user to inject random values for a field(**Type Node**) when defining a **Typegraph**. #### Migration notes _No changes needed_. <!-- Explain HOW users should update their code when required --> ### Checklist - [x] The change come with new or modified tests - [x] Hard-to-understand functions have explanatory comments - [ ] End-user documentation is updated to reflect the change
1 parent 961fab5 commit bbf0b95

File tree

28 files changed

+461
-53
lines changed

28 files changed

+461
-53
lines changed

libs/common/src/typegraph/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub struct TypeMeta {
9898
pub auths: Vec<Auth>,
9999
pub rate: Option<Rate>,
100100
pub version: String,
101+
pub random_seed: Option<u32>,
101102
}
102103

103104
#[cfg_attr(feature = "codegen", derive(JsonSchema))]

libs/common/src/typegraph/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub enum Injection {
5050
Secret(InjectionData<String>),
5151
Parent(InjectionData<u32>),
5252
Dynamic(InjectionData<String>),
53+
Random(InjectionData<String>),
5354
}
5455

5556
#[cfg_attr(feature = "codegen", derive(JsonSchema))]
@@ -116,7 +117,6 @@ pub enum StringFormat {
116117
Ean,
117118
Date,
118119
DateTime,
119-
// Path,
120120
Phone,
121121
}
122122

typegate/deno.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typegate/src/engine/planner/args.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class ArgumentCollector {
245245
private collectArgImpl(node: CollectNode): ComputeArg {
246246
const { astNode, typeIdx } = node;
247247

248-
const typ = this.tg.type(typeIdx);
248+
const typ: TypeNode = this.tg.type(typeIdx);
249249

250250
this.addPoliciesFrom(typeIdx);
251251

@@ -695,6 +695,10 @@ class ArgumentCollector {
695695
}
696696
return generator;
697697
}
698+
699+
case "random": {
700+
return () => this.tg.getRandom(typ);
701+
}
698702
}
699703
}
700704

typegate/src/runtimes/random.ts

Lines changed: 94 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -78,48 +78,106 @@ export class RandomRuntime extends Runtime {
7878

7979
execute(typ: TypeNode): Resolver {
8080
return () => {
81-
return this.randomizeRecursively(typ);
81+
return randomizeRecursively(typ, this.chance, this._tgTypes);
8282
};
8383
}
84+
}
8485

85-
randomizeRecursively(typ: TypeNode): any {
86-
const config = typ.config ?? {};
87-
if (Object.prototype.hasOwnProperty.call(config, "gen")) {
88-
const { gen, ...arg } = config;
89-
return this.chance[gen as string](arg);
86+
export default function randomizeRecursively(
87+
typ: TypeNode,
88+
chance: typeof Chance,
89+
tgTypes: TypeNode[],
90+
): any {
91+
const config = typ.config ?? {};
92+
if (Object.prototype.hasOwnProperty.call(config, "gen")) {
93+
const { gen, ...arg } = config;
94+
return chance[gen as string](arg);
95+
}
96+
switch (typ.type) {
97+
case "object":
98+
return {};
99+
case "optional": {
100+
const childNodeName = tgTypes[typ.item];
101+
return chance.bool()
102+
? randomizeRecursively(childNodeName, chance, tgTypes)
103+
: null;
90104
}
91-
switch (typ.type) {
92-
case "object":
93-
return {};
94-
case "optional": {
95-
const childNodeName = this.getTgTypeNameByIndex(typ.item);
96-
return this.chance.bool()
97-
? this.randomizeRecursively(childNodeName)
98-
: null;
105+
case "integer":
106+
return chance.integer();
107+
case "string":
108+
if (typ.format === "uuid") {
109+
return chance.guid();
110+
}
111+
if (typ.format === "email") {
112+
return chance.email();
113+
}
114+
if (typ.format === "uri") {
115+
return chance.url();
116+
}
117+
if (typ.format === "hostname") {
118+
return chance.domain();
119+
}
120+
if (typ.format === "date-time") {
121+
const randomDate = chance.date();
122+
123+
// Get the timestamp of the random date
124+
const timestamp = randomDate.getTime();
125+
console.log(randomDate);
126+
127+
// Create a new Date object with the timestamp adjusted for the local timezone offset
128+
const dateInUtc = new Date(
129+
timestamp - randomDate.getTimezoneOffset() * 60000,
130+
);
131+
return dateInUtc.toISOString();
132+
}
133+
if (typ.format === "phone") {
134+
return chance.phone();
99135
}
100-
case "integer":
101-
return this.chance.integer();
102-
case "string":
103-
if (typ.format === "uuid") {
104-
return this.chance.guid();
105-
}
106-
if (typ.format === "email") {
107-
return this.chance.email();
108-
}
109-
return this.chance.string();
110-
case "boolean":
111-
return this.chance.bool();
112-
case "list": {
113-
const res = [];
114-
let size = this.chance.integer({ min: 1, max: 10 });
115-
const childNodeName = this.getTgTypeNameByIndex(typ.items);
116-
while (size--) {
117-
res.push(this.randomizeRecursively(childNodeName));
118-
}
119-
return res;
136+
if (typ.format == "ean") {
137+
return generateEAN(chance);
120138
}
121-
default:
122-
throw new Error(`type not supported "${typ.type}"`);
139+
return chance.string();
140+
case "boolean":
141+
return chance.bool();
142+
case "list": {
143+
const res = [];
144+
let size = chance.integer({ min: 1, max: 10 });
145+
const childNodeName = tgTypes[typ.items];
146+
while (size--) {
147+
res.push(
148+
randomizeRecursively(childNodeName, chance, tgTypes),
149+
);
150+
}
151+
return res;
123152
}
153+
154+
default:
155+
throw new Error(`type not supported "${typ.type}"`);
124156
}
125157
}
158+
159+
function generateEAN(chance: typeof Chance) {
160+
let ean = "0";
161+
162+
for (let i = 1; i <= 11; i++) {
163+
ean += chance.integer({ min: 0, max: 9 }).toString();
164+
}
165+
166+
const checkDigit = calculateCheckDigit(ean);
167+
ean += checkDigit;
168+
169+
return ean;
170+
}
171+
172+
function calculateCheckDigit(ean: string) {
173+
const digits = ean.split("").map(Number);
174+
175+
let sum = 0;
176+
for (let i = 0; i < digits.length; i++) {
177+
sum += (i % 2 === 0) ? digits[i] : digits[i] * 3;
178+
}
179+
180+
const checkDigit = (10 - (sum % 10)) % 10;
181+
182+
return checkDigit.toString();
183+
}

typegate/src/typegraph/mod.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { DenoRuntime } from "../runtimes/deno/deno.ts";
77
import { Runtime } from "../runtimes/Runtime.ts";
88
import { ensure, ensureNonNullable } from "../utils.ts";
99
import { typegraph_validate } from "native";
10+
import Chance from "chance";
1011

1112
import {
1213
initAuth,
@@ -41,6 +42,7 @@ import type {
4142
import { InternalAuth } from "../services/auth/protocols/internal.ts";
4243
import { Protocol } from "../services/auth/protocols/protocol.ts";
4344
import { initRuntime } from "../runtimes/mod.ts";
45+
import randomizeRecursively from "../runtimes/random.ts";
4446

4547
export { Cors, Rate, TypeGraphDS, TypeMaterializer, TypePolicy, TypeRuntime };
4648

@@ -349,6 +351,30 @@ export class TypeGraph {
349351
);
350352
}
351353

354+
getRandom(
355+
schema: TypeNode,
356+
): number | string | null {
357+
const tgTypes: TypeNode[] = this.tg.types;
358+
let seed = 12; // default seed
359+
if (
360+
this.tg.meta.random_seed !== undefined &&
361+
this.tg.meta.random_seed !== null
362+
) {
363+
seed = this.tg.meta.random_seed;
364+
}
365+
const chance: typeof Chance = new Chance(seed);
366+
367+
try {
368+
const result = randomizeRecursively(schema, chance, tgTypes);
369+
370+
return result;
371+
} catch (_) {
372+
throw new Error(
373+
`invalid type for random injection: ${schema.type}`,
374+
);
375+
}
376+
}
377+
352378
nextBatcher = (
353379
type: TypeNode,
354380
): Batcher => {

typegate/src/typegraph/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ export type Injection = {
223223
} | {
224224
source: "dynamic";
225225
data: InjectionDataFor_String;
226+
} | {
227+
source: "random";
228+
data: InjectionDataFor_String;
226229
};
227230
export type InjectionDataFor_String = SingleValueFor_String | {
228231
[k: string]: string;
@@ -503,6 +506,7 @@ export interface TypeMeta {
503506
auths: Auth[];
504507
rate?: Rate | null;
505508
version: string;
509+
random_seed: number | undefined;
506510
}
507511
export interface Queries {
508512
dynamic: boolean;

typegate/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ snapshot[`typegraphs creation 1`] = `
326326
"context_identifier": "user",
327327
"local_excess": 5
328328
},
329-
"version": "0.0.3"
329+
"version": "0.0.3",
330+
"random_seed": null
330331
}
331332
}
332333
]'
@@ -533,7 +534,8 @@ snapshot[`typegraphs creation 2`] = `
533534
},
534535
"auths": [],
535536
"rate": null,
536-
"version": "0.0.3"
537+
"version": "0.0.3",
538+
"random_seed": null
537539
}
538540
}
539541
]\`
@@ -815,7 +817,8 @@ snapshot[`typegraphs creation 3`] = `
815817
},
816818
"auths": [],
817819
"rate": null,
818-
"version": "0.0.3"
820+
"version": "0.0.3",
821+
"random_seed": null
819822
}
820823
}
821824
]\`
@@ -1147,7 +1150,8 @@ snapshot[`typegraphs creation 4`] = `
11471150
"context_identifier": "user",
11481151
"local_excess": 5
11491152
},
1150-
"version": "0.0.3"
1153+
"version": "0.0.3",
1154+
"random_seed": null
11511155
}
11521156
}
11531157
]'
@@ -1354,7 +1358,8 @@ snapshot[`typegraphs creation 5`] = `
13541358
},
13551359
"auths": [],
13561360
"rate": null,
1357-
"version": "0.0.3"
1361+
"version": "0.0.3",
1362+
"random_seed": null
13581363
}
13591364
}
13601365
]\`
@@ -1636,7 +1641,8 @@ snapshot[`typegraphs creation 6`] = `
16361641
},
16371642
"auths": [],
16381643
"rate": null,
1639-
"version": "0.0.3"
1644+
"version": "0.0.3",
1645+
"random_seed": null
16401646
}
16411647
}
16421648
]\`
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typegraph.policy import Policy
2+
from typegraph.runtimes.deno import DenoRuntime
3+
4+
from typegraph import Graph, t, typegraph
5+
6+
7+
@typegraph()
8+
def random_injection(g: Graph):
9+
pub = Policy.public()
10+
deno = DenoRuntime()
11+
12+
user = t.struct(
13+
{
14+
"id": t.uuid().from_random(),
15+
"ean": t.ean().from_random(),
16+
"name": t.string(config={"gen": "name"}).from_random(),
17+
"age": t.integer(config={"gen": "age", "type": "adult"}).from_random(),
18+
"married": t.boolean().from_random(),
19+
"birthday": t.datetime().from_random(),
20+
"friends": t.list(t.string(config={"gen": "first"})).from_random(),
21+
"phone": t.string(config={"gen": "phone"}).from_random(),
22+
"gender": t.string(config={"gen": "gender"}).from_random(),
23+
"firstname": t.string(config={"gen": "first"}).from_random(),
24+
"lastname": t.string(config={"gen": "last"}).from_random(),
25+
"occupation": t.string(config={"gen": "profession"}).from_random(),
26+
"street": t.string(config={"gen": "address"}).from_random(),
27+
"city": t.string(config={"gen": "city"}).from_random(),
28+
"postcode": t.string(config={"gen": "postcode"}).from_random(),
29+
"country": t.string(
30+
config={"gen": "country", "full": "true"}
31+
).from_random(),
32+
"uri": t.uri().from_random(),
33+
"hostname": t.string(format="hostname").from_random(),
34+
}
35+
)
36+
37+
random_list = t.struct(
38+
{
39+
"names": t.list(t.string(config={"gen": "name"})).from_random(),
40+
}
41+
)
42+
# Configure random injection seed value or the default will be used
43+
g.configure_random_injection(seed=1)
44+
g.expose(
45+
pub,
46+
randomUser=deno.identity(user),
47+
randomList=deno.identity(random_list),
48+
)

0 commit comments

Comments
 (0)