From 37f51aa4390c3225de0745c9384be724ce9f3b8b Mon Sep 17 00:00:00 2001 From: michael-0acf4 Date: Sat, 21 Dec 2024 23:27:17 +0300 Subject: [PATCH] feat(gate,sdk)!: new policy spec (#937) #### Migration notes * Replaced true, false, and null to ALLOW, DENY and PASS. Composition rules: 1. On traversal order: * `ALLOW`: allow parent and all its children (ignore inner policies) * `DENY`: deny parent and all its children (ignore inner policies) * `PASS`: pass through parent and evaluate each children (no-op, equivalent to no policies) 2. On a single type (a.with_policy(X).with_policy(Y)): `ALLOW` and `DENY` compose the same as true and false with the AND gate, `PASS` does not participate. * `ALLOW` & P = P * `DENY` & P = `DENY` (e.g. DENY & ALLOW = DENY) * `PASS` & P = P (does not participate) - [x] The change comes with new or modified tests - [x] Hard-to-understand functions have explanatory comments - [x] End-user documentation is updated to reflect the change ## Summary by CodeRabbit ## Release Notes - **New Features** - Enhanced documentation for Metatype's mental model, including clearer policy definitions and a comparison table with classical models. - Introduction of a comprehensive tutorial on building a Metatype API, covering setup, CRUD operations, and security practices. - **Bug Fixes** - Updated policy logic to return explicit 'ALLOW' or 'DENY' strings instead of boolean values across various components. - **Documentation** - Improved clarity and detail in documentation for policies and core concepts. - Added new sections for policy composition rules and traversal order. - **Refactor** - Streamlined policy management and evaluation logic across multiple files, enhancing clarity and maintainability. - **Tests** - Added tests for new policy functionalities and updated existing tests to reflect changes in policy handling. --------- Co-authored-by: Natoandro --- .../docs/concepts/mental-model/index.mdx | 6 +- .../docs/reference/policies/index.mdx | 18 +- .../docs/tutorials/metatype-basics/index.mdx | 4 +- examples/typegraphs/execute.py | 2 +- examples/typegraphs/execute.ts | 2 +- examples/typegraphs/func.py | 2 +- examples/typegraphs/func.ts | 2 +- examples/typegraphs/math.py | 2 +- examples/typegraphs/math.ts | 2 +- examples/typegraphs/policies-example.py | 7 +- examples/typegraphs/policies.py | 8 +- examples/typegraphs/policies.ts | 8 +- .../typegraphs/programmable-api-gateway.py | 4 +- .../typegraphs/programmable-api-gateway.ts | 2 +- examples/typegraphs/reduce.py | 2 +- examples/typegraphs/reduce.ts | 2 +- examples/typegraphs/rest.py | 2 +- examples/typegraphs/rest.ts | 2 +- examples/typegraphs/roadmap-policies.py | 2 +- examples/typegraphs/roadmap-policies.ts | 2 +- src/common/src/typegraph/types.rs | 4 +- src/metagen/src/fdk_rust/stubs.rs | 1 + src/metagen/src/fdk_rust/types.rs | 8 + src/metagen/src/tests/fixtures.rs | 2 +- src/typegate/src/engine/planner/args.ts | 42 +- src/typegate/src/engine/planner/mod.ts | 67 +- src/typegate/src/engine/planner/policies.ts | 570 +++++++++++------- src/typegate/src/engine/typecheck/result.ts | 1 - src/typegate/src/runtimes/deno/deno.ts | 6 +- src/typegate/src/runtimes/typegate.ts | 41 +- src/typegate/src/runtimes/typegraph.ts | 107 ++-- .../src/transports/graphql/typegraph.ts | 77 ++- src/typegate/src/typegraph/mod.ts | 1 - src/typegate/src/typegraph/types.ts | 13 +- .../src/typegraphs/introspection.json | 441 ++++---------- .../src/typegraphs/prisma_migration.json | 242 +++----- .../src/typegraphs/prisma_migration.py | 3 +- src/typegate/src/typegraphs/typegate.json | 495 ++++++++------- src/typegate/src/typegraphs/typegate.py | 7 +- src/typegraph/core/src/conversion/types.rs | 15 +- src/typegraph/core/src/global_store.rs | 6 +- src/typegraph/core/src/lib.rs | 9 +- ...core__tests__successful_serialization.snap | 19 +- src/typegraph/core/src/typedef/boolean.rs | 6 +- src/typegraph/core/src/typedef/either.rs | 4 +- src/typegraph/core/src/typedef/file.rs | 6 +- src/typegraph/core/src/typedef/float.rs | 6 +- src/typegraph/core/src/typedef/func.rs | 2 - src/typegraph/core/src/typedef/integer.rs | 6 +- src/typegraph/core/src/typedef/list.rs | 4 +- src/typegraph/core/src/typedef/optional.rs | 4 +- src/typegraph/core/src/typedef/string.rs | 6 +- src/typegraph/core/src/typedef/struct_.rs | 70 ++- src/typegraph/core/src/typedef/union.rs | 4 +- src/typegraph/core/src/typegraph.rs | 10 +- .../core/src/validation/materializers.rs | 12 + src/typegraph/deno/src/types.ts | 9 +- src/typegraph/python/typegraph/t.py | 5 +- tests/auth/auth.py | 7 +- .../__snapshots__/typegraph_test.ts.snap | 296 ++++----- tests/e2e/typegraph/typegraphs/deno/simple.ts | 2 +- .../e2e/typegraph/typegraphs/python/simple.py | 2 +- .../__snapshots__/planner_test.ts.snap | 132 ++-- tests/planner/planner_test.ts | 18 +- tests/policies/effects_py.py | 4 +- tests/policies/policies.py | 17 +- tests/policies/policies_composition.py | 87 +++ tests/policies/policies_composition_test.ts | 252 ++++++++ tests/policies/policies_test.ts | 23 +- .../__snapshots__/query_parsers_test.ts.snap | 96 +-- .../__snapshots__/graphql_test.ts.snap | 296 +++++---- .../grpc/__snapshots__/grpc_test.ts.snap | 58 +- .../runtimes/kv/__snapshots__/kv_test.ts.snap | 128 ++-- .../runtimes/s3/__snapshots__/s3_test.ts.snap | 103 ++-- .../__snapshots__/temporal_test.ts.snap | 171 +++--- .../typegate_prisma_test.ts.snap | 54 +- .../runtimes/typegate/typegate_prisma_test.ts | 5 +- tests/utils/bindings_test.ts | 16 +- 78 files changed, 2374 insertions(+), 1805 deletions(-) create mode 100644 tests/policies/policies_composition.py create mode 100644 tests/policies/policies_composition_test.ts diff --git a/docs/metatype.dev/docs/concepts/mental-model/index.mdx b/docs/metatype.dev/docs/concepts/mental-model/index.mdx index 4d1032b73c..fb947e7f1d 100644 --- a/docs/metatype.dev/docs/concepts/mental-model/index.mdx +++ b/docs/metatype.dev/docs/concepts/mental-model/index.mdx @@ -91,9 +91,9 @@ Policies are a special type of function `t.func(t.struct({...}), t.boolean().opt The policy decision can be: -- `true`: the access is authorized -- `false`: the access is denied -- `null`: the access in inherited from the parent types +- `ALLOW`: Grants access to the current type and all its descendants. +- `DENY`: Restricts access to the current type and all its descendants. +- `PASS`: Grants access to the current type while requiring individual checks for all its descendants (similar to the absence of policies). { diff --git a/docs/metatype.dev/docs/reference/policies/index.mdx b/docs/metatype.dev/docs/reference/policies/index.mdx index 8bc5ace886..e5f49035c5 100644 --- a/docs/metatype.dev/docs/reference/policies/index.mdx +++ b/docs/metatype.dev/docs/reference/policies/index.mdx @@ -17,8 +17,8 @@ The Deno runtime enable to understand the last abstraction. Policies are a way t Metatype comes with some built-in policies, but you can use the Deno runtime to define your own: -- `policies.public()` is an alias for `Policy(PureFunMat("() => true"))` providing everyone open access. -- `policies.ctx("role_value", "role_field")` is a companion policy for the authentication strategy you learned in the previous section. It will verify the context and give adequate access to the user. +- `policies.public()` is an alias for `deno.policy("public", "() => 'PASS'")` providing everyone open access while still allowing field level custom access. +- `Policy.context("role_value", "role_field")` is a companion policy for the authentication strategy you learned in the previous section. It will verify the context and give adequate access to the user. Policies are hierarchical in the sense that the request starts with a denial, and the root functions must explicitly provide an access or not. Once access granted, any further types can either inherit or override the access. Policies evaluate in order in case multiple ones are defined. @@ -28,3 +28,17 @@ Policies are hierarchical in the sense that the request starts with a denial, an typescript={require("!!code-loader!../../../../../examples/typegraphs/policies.ts")} query={require("./policies.graphql")} /> + +## Composition rules + +### Traversal order + +- `ALLOW`: Allows access to the parent and all its descendants, disregarding inner policies. +- `DENY`: Denies access to the parent and all its descendants, disregarding inner policies. +- `PASS`: Allows access to the parent, each descendant will still be evaluated individually (equivalent to having no policies set). + +### Chaining policies + +If you have `foo.with_policy(A, B).with_policy(C)` for example, it will evaluated in batch as `[A, B, C]`. + +If one or more policies fail (`DENY`), the type will be inaccessible. diff --git a/docs/metatype.dev/docs/tutorials/metatype-basics/index.mdx b/docs/metatype.dev/docs/tutorials/metatype-basics/index.mdx index 228bdd8709..d3f87ac184 100644 --- a/docs/metatype.dev/docs/tutorials/metatype-basics/index.mdx +++ b/docs/metatype.dev/docs/tutorials/metatype-basics/index.mdx @@ -581,7 +581,7 @@ typegraph("roadmap", (g) => { const admins = deno.policy( "admins", ` - (_args, { context }) => !!context.username + (_args, { context }) => !!context.username ? 'ALLOW' : 'DENY' `, ); @@ -619,7 +619,7 @@ def roadmap(g: Graph): # the username value is only available if the basic # extractor was successful admins = deno.policy("admins", """ - (_args, { context }) => !!context.username + (_args, { context }) => !!context.username ? 'ALLOW' : 'DENY' """) g.expose( diff --git a/examples/typegraphs/execute.py b/examples/typegraphs/execute.py index ed46b99712..84284fa0a0 100644 --- a/examples/typegraphs/execute.py +++ b/examples/typegraphs/execute.py @@ -53,7 +53,7 @@ def roadmap(g: Graph): admins = deno.policy( "admins", """ - (_args, { context }) => !!context.username + (_args, { context }) => !!context.username ? 'PASS' : 'DENY' """, ) diff --git a/examples/typegraphs/execute.ts b/examples/typegraphs/execute.ts index b78951bbe8..1e61d05056 100644 --- a/examples/typegraphs/execute.ts +++ b/examples/typegraphs/execute.ts @@ -52,7 +52,7 @@ await typegraph( const admins = deno.policy( "admins", ` - (_args, { context }) => !!context.username + (_args, { context }) => !!context.username ? 'PASS' : 'DENY' `, ); diff --git a/examples/typegraphs/func.py b/examples/typegraphs/func.py index 5ad86ba026..cfc4cda609 100644 --- a/examples/typegraphs/func.py +++ b/examples/typegraphs/func.py @@ -58,7 +58,7 @@ def roadmap(g: Graph): admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ) # skip:end diff --git a/examples/typegraphs/func.ts b/examples/typegraphs/func.ts index 944d6d7c9a..90ccdfb287 100644 --- a/examples/typegraphs/func.ts +++ b/examples/typegraphs/func.ts @@ -56,7 +56,7 @@ await typegraph( const admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ); // skip:end diff --git a/examples/typegraphs/math.py b/examples/typegraphs/math.py index 054954b87e..90fe78a26a 100644 --- a/examples/typegraphs/math.py +++ b/examples/typegraphs/math.py @@ -27,7 +27,7 @@ def math(g: Graph): # the policy implementation is based on functions as well restrict_referer = deno.policy( "restrict_referer_policy", - '(_, context) => context.headers.referer && ["localhost", "metatype.dev"].includes(new URL(context.headers.referer).hostname)', + '(_, context) => context.headers.referer && ["localhost", "metatype.dev"].includes(new URL(context.headers.referer).hostname) ? "ALLOW" : "DENY"', ) g.expose( diff --git a/examples/typegraphs/math.ts b/examples/typegraphs/math.ts index 3c7879c5e7..c0bc7f8153 100644 --- a/examples/typegraphs/math.ts +++ b/examples/typegraphs/math.ts @@ -24,7 +24,7 @@ await typegraph( // the policy implementation is based on functions itself const restrict_referer = deno.policy( "restrict_referer_policy", - '(_, context) => context.headers.referer && ["localhost", "metatype.dev"].includes(new URL(context.headers.referer).hostname)', + '(_, context) => context.headers.referer && ["localhost", "metatype.dev"].includes(new URL(context.headers.referer).hostname) ? "ALLOW" : "DENY"', ); // or we can point to a local file that's accessible to the meta-cli diff --git a/examples/typegraphs/policies-example.py b/examples/typegraphs/policies-example.py index 749f540f5f..cc3a2ff25f 100644 --- a/examples/typegraphs/policies-example.py +++ b/examples/typegraphs/policies-example.py @@ -10,5 +10,8 @@ def policies_example(g): # skip:end deno = DenoRuntime() - public = deno.policy("public", "() => true") # noqa - team_only = deno.policy("team", "(ctx) => ctx.user.role === 'admin'") # noqa + public = deno.policy("public", "() => 'PASS'") # noqa + allow_all = deno.policy("allow_all", "() => 'ALLOW'") # noqa + team_only = deno.policy( # noqa + "team", "(ctx) => ctx.user.role === 'admin' ? 'ALLOW' : 'DENY' " + ) diff --git a/examples/typegraphs/policies.py b/examples/typegraphs/policies.py index 35a53d0773..a2557ba663 100644 --- a/examples/typegraphs/policies.py +++ b/examples/typegraphs/policies.py @@ -15,17 +15,17 @@ def policies(g: Graph): deno = DenoRuntime() random = RandomRuntime(seed=0, reset=None) - # `public` is sugar for to `() => true` + # `public` is sugar for to `(_args, _ctx) => "PASS"` public = Policy.public() admin_only = deno.policy( "admin_only", - # note: policies either return true | false | null - "(args, { context }) => context.username ? context.username === 'admin' : null", + # note: policies either return "ALLOW" | "DENY" | "PASS" + "(args, { context }) => context?.username === 'admin' ? 'ALLOW' : 'DENY'", ) user_only = deno.policy( "user_only", - "(args, { context }) => context.username ? context.username === 'user' : null", + "(args, { context }) => context?.username === 'user' ? 'PASS' : 'DENY'", ) g.auth(Auth.basic(["admin", "user"])) diff --git a/examples/typegraphs/policies.ts b/examples/typegraphs/policies.ts index 1517a4d791..17c9394b26 100644 --- a/examples/typegraphs/policies.ts +++ b/examples/typegraphs/policies.ts @@ -13,17 +13,17 @@ typegraph( // skip:end const deno = new DenoRuntime(); const random = new RandomRuntime({ seed: 0 }); - // `public` is sugar for `(_args, _ctx) => true` + // `public` is sugar for `(_args, _ctx) => "PASS"` const pub = Policy.public(); const admin_only = deno.policy( "admin_only", - // note: policies either return true | false | null - "(args, { context }) => context.username ? context.username === 'admin' : null", + // note: policies either return "ALLOW" | "DENY" | "PASS" + "(args, { context }) => context?.username === 'admin' ? 'ALLOW' : 'DENY'", ); const user_only = deno.policy( "user_only", - "(args, { context }) => context.username ? context.username === 'user' : null", + "(args, { context }) => context?.username === 'user' ? 'PASS' : 'DENY'", ); g.auth(Auth.basic(["admin", "user"])); diff --git a/examples/typegraphs/programmable-api-gateway.py b/examples/typegraphs/programmable-api-gateway.py index 284c01b0d9..f788260583 100644 --- a/examples/typegraphs/programmable-api-gateway.py +++ b/examples/typegraphs/programmable-api-gateway.py @@ -17,7 +17,9 @@ def programmable_api_gateway(g: Graph): deno = DenoRuntime() public = Policy.public() - roulette_access = deno.policy("roulette", "() => Math.random() < 0.5") + roulette_access = deno.policy( + "roulette", "() => Math.random() < 0.5 ? 'PASS' : 'DENY'" + ) my_api_format = """ static_a: diff --git a/examples/typegraphs/programmable-api-gateway.ts b/examples/typegraphs/programmable-api-gateway.ts index 08b5fd0038..ebaf52df37 100644 --- a/examples/typegraphs/programmable-api-gateway.ts +++ b/examples/typegraphs/programmable-api-gateway.ts @@ -17,7 +17,7 @@ typegraph( const pub = Policy.public(); const roulette_access = deno.policy( "roulette", - "() => Math.random() < 0.5", + "() => Math.random() < 0.5 ? 'PASS' : 'DENY'", ); // skip:next-line diff --git a/examples/typegraphs/reduce.py b/examples/typegraphs/reduce.py index f8e0993216..62afe9c6f9 100644 --- a/examples/typegraphs/reduce.py +++ b/examples/typegraphs/reduce.py @@ -51,7 +51,7 @@ def roadmap(g: Graph): admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ) g.expose( diff --git a/examples/typegraphs/reduce.ts b/examples/typegraphs/reduce.ts index b485e7ac83..0460066d8b 100644 --- a/examples/typegraphs/reduce.ts +++ b/examples/typegraphs/reduce.ts @@ -49,7 +49,7 @@ typegraph( const admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ); g.expose( diff --git a/examples/typegraphs/rest.py b/examples/typegraphs/rest.py index c0b62c8dce..1b3aff97d5 100644 --- a/examples/typegraphs/rest.py +++ b/examples/typegraphs/rest.py @@ -51,7 +51,7 @@ def roadmap(g: Graph): admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ) g.expose( diff --git a/examples/typegraphs/rest.ts b/examples/typegraphs/rest.ts index 75e4bd3e79..ec242e798b 100644 --- a/examples/typegraphs/rest.ts +++ b/examples/typegraphs/rest.ts @@ -49,7 +49,7 @@ typegraph( const admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ); g.expose( diff --git a/examples/typegraphs/roadmap-policies.py b/examples/typegraphs/roadmap-policies.py index 791355c2b6..dde6b51f98 100644 --- a/examples/typegraphs/roadmap-policies.py +++ b/examples/typegraphs/roadmap-policies.py @@ -56,7 +56,7 @@ def roadmap(g: Graph): # highlight-start admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ) # highlight-end diff --git a/examples/typegraphs/roadmap-policies.ts b/examples/typegraphs/roadmap-policies.ts index 6cb0ef1858..458ebd9dbf 100644 --- a/examples/typegraphs/roadmap-policies.ts +++ b/examples/typegraphs/roadmap-policies.ts @@ -50,7 +50,7 @@ typegraph( const admins = deno.policy( "admins", - "(_args, { context }) => !!context.username", + "(_args, { context }) => !!context.username ? 'PASS' : 'DENY'", ); g.expose( diff --git a/src/common/src/typegraph/types.rs b/src/common/src/typegraph/types.rs index 22d2b3da75..1cd144886d 100644 --- a/src/common/src/typegraph/types.rs +++ b/src/common/src/typegraph/types.rs @@ -63,7 +63,6 @@ pub enum Injection { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TypeNodeBase { pub title: String, - pub policies: Vec, #[serde(default)] pub description: Option, #[serde(default, rename = "enum")] @@ -158,6 +157,9 @@ pub struct ObjectTypeData { pub id: Vec, #[serde(default)] pub required: Vec, + #[serde(skip_serializing_if = "IndexMap::is_empty")] + #[serde(default)] + pub policies: IndexMap>, } #[skip_serializing_none] diff --git a/src/metagen/src/fdk_rust/stubs.rs b/src/metagen/src/fdk_rust/stubs.rs index ad636ef45b..ed91fea2ef 100644 --- a/src/metagen/src/fdk_rust/stubs.rs +++ b/src/metagen/src/fdk_rust/stubs.rs @@ -113,6 +113,7 @@ mod test { TypeNode::Object { data: ObjectTypeData { properties: Default::default(), + policies: Default::default(), id: vec![], required: vec![], }, diff --git a/src/metagen/src/fdk_rust/types.rs b/src/metagen/src/fdk_rust/types.rs index 9d7d2db91a..417b6aeddf 100644 --- a/src/metagen/src/fdk_rust/types.rs +++ b/src/metagen/src/fdk_rust/types.rs @@ -438,6 +438,7 @@ mod test { ] .into_iter() .collect(), + policies: Default::default(), id: vec![], // FIXME: remove required required: vec![], @@ -615,6 +616,7 @@ pub enum MyUnion { properties: [("obj_b".to_string(), 1)].into_iter().collect(), id: vec![], required: ["obj_b"].into_iter().map(Into::into).collect(), + policies: Default::default(), }, base: TypeNodeBase { title: "ObjA".into(), @@ -624,6 +626,7 @@ pub enum MyUnion { TypeNode::Object { data: ObjectTypeData { properties: [("obj_c".to_string(), 2)].into_iter().collect(), + policies: Default::default(), id: vec![], required: ["obj_c"].into_iter().map(Into::into).collect(), }, @@ -635,6 +638,7 @@ pub enum MyUnion { TypeNode::Object { data: ObjectTypeData { properties: [("obj_a".to_string(), 0)].into_iter().collect(), + policies: Default::default(), id: vec![], required: ["obj_a"].into_iter().map(Into::into).collect(), }, @@ -665,6 +669,7 @@ pub struct ObjC { TypeNode::Object { data: ObjectTypeData { properties: [("obj_b".to_string(), 1)].into_iter().collect(), + policies: Default::default(), id: vec![], required: ["obj_b"].into_iter().map(Into::into).collect(), }, @@ -676,6 +681,7 @@ pub struct ObjC { TypeNode::Object { data: ObjectTypeData { properties: [("union_c".to_string(), 2)].into_iter().collect(), + policies: Default::default(), id: vec![], required: ["union_c"].into_iter().map(Into::into).collect(), }, @@ -714,6 +720,7 @@ pub enum CUnion { TypeNode::Object { data: ObjectTypeData { properties: [("obj_b".to_string(), 1)].into_iter().collect(), + policies: Default::default(), id: vec![], required: ["obj_b"].into_iter().map(Into::into).collect(), }, @@ -725,6 +732,7 @@ pub enum CUnion { TypeNode::Object { data: ObjectTypeData { properties: [("either_c".to_string(), 2)].into_iter().collect(), + policies: Default::default(), id: vec![], required: ["either_c"].into_iter().map(Into::into).collect(), }, diff --git a/src/metagen/src/tests/fixtures.rs b/src/metagen/src/tests/fixtures.rs index 7dcb060b22..2e70a56707 100644 --- a/src/metagen/src/tests/fixtures.rs +++ b/src/metagen/src/tests/fixtures.rs @@ -55,6 +55,7 @@ pub fn test_typegraph_2() -> Typegraph { TypeNode::Object { data: ObjectTypeData { properties: Default::default(), + policies: Default::default(), id: vec![], required: vec![], }, @@ -99,7 +100,6 @@ pub fn test_typegraph_2() -> Typegraph { pub fn default_type_node_base() -> TypeNodeBase { TypeNodeBase { title: String::new(), - policies: vec![], description: None, enumeration: None, } diff --git a/src/typegate/src/engine/planner/args.ts b/src/typegate/src/engine/planner/args.ts index baa9cc1e78..f770930dc5 100644 --- a/src/typegate/src/engine/planner/args.ts +++ b/src/typegate/src/engine/planner/args.ts @@ -29,6 +29,7 @@ import type { FunctionNode, Injection, InjectionNode, + PolicyIndices, } from "../../typegraph/types.ts"; import { getChildTypes, visitTypes } from "../../typegraph/visitor.ts"; @@ -82,6 +83,7 @@ interface CollectNode { path: string[]; astNode: ast.ArgumentNode | ast.ObjectFieldNode | undefined; typeIdx: number; + policies: PolicyIndices[]; } interface CollectedArgs { @@ -120,6 +122,7 @@ export function collectArgs( path: [argName], astNode: astNodes[argName], typeIdx: argTypeIdx, + policies: argTypeNode.policies?.[argName] ?? [], }); } @@ -298,7 +301,7 @@ class ArgumentCollector { const typ: TypeNode = this.tg.type(typeIdx); - this.addPoliciesFrom(typeIdx); + this.addPoliciesFrom(typeIdx, node.policies); const injection = this.#getInjection(node.path); if (injection != null) { @@ -319,7 +322,6 @@ class ArgumentCollector { // try to get a default value for it, else throw an error if (astNode == null) { if (typ.type === Type.OPTIONAL) { - this.addPoliciesFrom(typ.item); const itemType = this.tg.type(typ.item); const { default_value: defaultValue } = typ; if (defaultValue != null) { @@ -332,6 +334,7 @@ class ArgumentCollector { this.collectDefaults( itemType.properties, node.path, + itemType.policies ?? {}, ), true, ); @@ -349,6 +352,7 @@ class ArgumentCollector { this.collectDefaults( variantType.properties, node.path, + variantType.policies ?? {}, ), true, ); @@ -372,6 +376,7 @@ class ArgumentCollector { compute = this.collectDefaults( variantType.properties, node.path, + variantType.policies ?? {}, ); break; } catch (_e) { @@ -390,7 +395,7 @@ class ArgumentCollector { if (typ.type === Type.OBJECT) { const props = typ.properties; - return this.collectDefaults(props, node.path); + return this.collectDefaults(props, node.path, typ.policies ?? {}); } throw new MandatoryArgumentError(this.currentNodeDetails); @@ -689,12 +694,14 @@ class ArgumentCollector { private collectDefaults( props: Record, path: string[], + policies: Record, ): ComputeArg> { const computes: Record = {}; for (const [name, idx] of Object.entries(props)) { path.push(name); computes[name] = this.collectDefault(idx, path); + this.addPoliciesFrom(idx, policies[name] ?? []); path.pop(); } @@ -723,7 +730,6 @@ class ArgumentCollector { path: string[], ): ComputeArg { const typ = this.tg.type(typeIdx); - this.addPoliciesFrom(typeIdx); const injection = this.#getInjection(path); if (injection != null) { @@ -737,13 +743,12 @@ class ArgumentCollector { switch (typ.type) { case Type.OPTIONAL: { - this.addPoliciesFrom(typ.item); const { default_value: defaultValue = null } = typ; return () => defaultValue; } case Type.OBJECT: { - return this.collectDefaults(typ.properties, path); + return this.collectDefaults(typ.properties, path, typ.policies ?? {}); } default: @@ -756,8 +761,12 @@ class ArgumentCollector { typ: TypeNode, injection: Injection, ): ComputeArg | null { - visitTypes(this.tg.tg, getChildTypes(typ), (node) => { - this.addPoliciesFrom(node.idx); + visitTypes(this.tg.tg, getChildTypes(typ), ({ type: typeNode }) => { + if (typeNode.type === Type.OBJECT) { + for (const [name, idx] of Object.entries(typeNode.properties)) { + this.addPoliciesFrom(idx, typeNode.policies?.[name] ?? []); + } + } return true; }); @@ -840,8 +849,12 @@ class ArgumentCollector { } this.deps.parent.add(key); - visitTypes(this.tg.tg, getChildTypes(typ), (node) => { - this.addPoliciesFrom(node.idx); + visitTypes(this.tg.tg, getChildTypes(typ), ({ type: typeNode }) => { + if (typeNode.type === Type.OBJECT) { + for (const [name, idx] of Object.entries(typeNode.properties)) { + this.addPoliciesFrom(idx, typeNode.policies?.[name] ?? []); + } + } return true; }); @@ -863,11 +876,14 @@ class ArgumentCollector { }; } - private addPoliciesFrom(typeIdx: TypeIdx) { - const typ = this.tg.type(typeIdx); + private addPoliciesFrom(typeIdx: TypeIdx, policies: PolicyIndices[]) { + if (policies.length === 0) { + return; + } + // TODO we might have to check for duplicate indices this.policies.set(typeIdx, { argDetails: this.currentNodeDetails, - policyIndices: typ.policies.map((p) => { + policyIndices: policies.map((p) => { if (typeof p === "number") { return p; } diff --git a/src/typegate/src/engine/planner/mod.ts b/src/typegate/src/engine/planner/mod.ts index aadabf533c..59d77e2a66 100644 --- a/src/typegate/src/engine/planner/mod.ts +++ b/src/typegate/src/engine/planner/mod.ts @@ -18,10 +18,7 @@ import { } from "../../typegraph/type_node.ts"; import { closestWord, unparse } from "../../utils.ts"; import { collectArgs, type ComputeArg } from "./args.ts"; -import { - type OperationPolicies, - OperationPoliciesBuilder, -} from "./policies.ts"; +import { OperationPolicies, StageMetadata } from "./policies.ts"; import { getLogger } from "../../log.ts"; import { generateVariantMatcher } from "../typecheck/matching_variant.ts"; import { mapValues } from "@std/collections/map-values"; @@ -29,7 +26,6 @@ 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); @@ -70,7 +66,6 @@ export interface Plan { */ export class Planner { rawArgs: Record = {}; - policiesBuilder: OperationPoliciesBuilder; constructor( readonly operation: ast.OperationDefinitionNode, @@ -78,10 +73,6 @@ export class Planner { private readonly tg: TypeGraph, private readonly verbose: boolean, ) { - const { timer_policy_eval_retries } = tg.typegate.config.base; - this.policiesBuilder = new OperationPoliciesBuilder(tg, { - timer_policy_eval_retries, - }); } getPlan(): Plan { @@ -112,13 +103,31 @@ export class Planner { {} as Record, ); + const orderedStageMetadata = [] as Array; for (const stage of stages) { stage.varTypes = varTypes; + const stageId = stage.id(); + if (stageId.startsWith("__")) { + // TODO: allow and reuse previous stage policy? + continue; + } + + orderedStageMetadata.push({ + stageId, + typeIdx: stage.props.typeIdx, + isTopLevel: stage.props.parent ? false : true, + node: stage.props.node // actual non aliased name + }); } + const { timer_policy_eval_retries } = this.tg.typegate.config.base; + const operationPolicies = new OperationPolicies(this.tg, orderedStageMetadata, { + timer_policy_eval_retries + }); + return { stages, - policies: this.policiesBuilder.build(), + policies: operationPolicies, }; } @@ -329,7 +338,10 @@ export class Planner { * @param field {FieldNode} The selection field for node * @param node */ - private traverseField(node: Node, field: ast.FieldNode): ComputeStage[] { + private traverseField( + node: Node, + field: ast.FieldNode, + ): ComputeStage[] { const { parent, path, name } = node; if (parent == null) { @@ -395,7 +407,6 @@ export class Planner { } const fieldType = this.tg.type(node.typeIdx); - const stages = fieldType.type !== Type.FUNCTION ? this.traverseValueField(node) : this.traverseFuncField( @@ -429,9 +440,10 @@ export class Planner { * 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[] { + private traverseValueField( + node: Node, + ): ComputeStage[] { const outjection = node.scope && this.#getOutjection(node.scope!); if (outjection) { return [ @@ -461,10 +473,6 @@ export class Planner { rateCalls: true, rateWeight: 0, }); - const types = this.policiesBuilder.setReferencedTypes( - stage.id(), - node.typeIdx, - ); stages.push(stage); @@ -474,7 +482,6 @@ export class Planner { while (isQuantifier(nestedSchema)) { nestedTypeIdx = getWrappedType(nestedSchema); nestedSchema = this.tg.type(nestedTypeIdx); - types.push(nestedTypeIdx); } stages.push(...this.traverse({ ...node, typeIdx: nestedTypeIdx }, stage)); @@ -560,31 +567,21 @@ export class Planner { }); stages.push(stage); - this.policiesBuilder.push(stage.id(), node.typeIdx, collected.policies); - const types = this.policiesBuilder.setReferencedTypes( - stage.id(), - node.typeIdx, - outputIdx, - inputIdx, - ); - // nested quantifiers - let wrappedTypeIdx = outputIdx; - let wrappedType = this.tg.type(wrappedTypeIdx); + let wrappedOutputIdx = outputIdx; + let wrappedType = this.tg.type(wrappedOutputIdx); while (isQuantifier(wrappedType)) { - wrappedTypeIdx = getWrappedType(wrappedType); - wrappedType = this.tg.type(wrappedTypeIdx); - types.push(wrappedTypeIdx); + wrappedOutputIdx = getWrappedType(wrappedType); + wrappedType = this.tg.type(wrappedOutputIdx); } stages.push( ...this.traverse( - { ...node, typeIdx: wrappedTypeIdx, parentStage: stage }, + { ...node, typeIdx: wrappedOutputIdx, parentStage: stage }, stage, ), ); - this.policiesBuilder.pop(stage.id()); return stages; } diff --git a/src/typegate/src/engine/planner/policies.ts b/src/typegate/src/engine/planner/policies.ts index d82cbcd8cc..420cdc5812 100644 --- a/src/typegate/src/engine/planner/policies.ts +++ b/src/typegate/src/engine/planner/policies.ts @@ -1,13 +1,13 @@ // Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. // SPDX-License-Identifier: MPL-2.0 +import { type Logger } from "@std/log"; import { DenoRuntime } from "../../runtimes/deno/deno.ts"; import type { TypeGraph } from "../../typegraph/mod.ts"; import type { Context, Info, PolicyIdx, - PolicyList, Resolver, StageId, TypeIdx, @@ -15,70 +15,97 @@ import type { import type { EffectType, PolicyIndices } from "../../typegraph/types.ts"; import { ensure } from "../../utils.ts"; import { getLogger } from "../../log.ts"; -import { Type } from "../../typegraph/type_node.ts"; -import type { ArgPolicies } from "./args.ts"; +import { + getWrappedType, + isEither, + isFunction, + isQuantifier, + isUnion, + Type, +} from "../../typegraph/type_node.ts"; import { BadContext } from "../../errors.ts"; -export interface FunctionSubtreeData { +export type PolicyResolverOutput = "DENY" | "ALLOW" | "PASS" | (unknown & {}); +type GetResolverResult = ( + polIdx: PolicyIdx, + effect: EffectType, +) => Promise; + +export interface StageMetadata { + stageId: string; typeIdx: TypeIdx; isTopLevel: boolean; - // types referenced in descendant nodes (that is not a descendent of a descendent function) - referencedTypes: Map>; + node: string; } -interface GetResolverResult { - (polIdx: PolicyIdx, effect: EffectType): Promise; +interface ComposePolicyOperand { + /** Field name according to the underlying struct on the typegraph */ + canonFieldName: string; + /** The actual policy index selected against a given effect */ + index: PolicyIdx; } type CheckResult = - | { authorized: true } + | { authorized: "ALLOW" } + | { authorized: "PASS" } | { - authorized: false; - policyIdx: PolicyIdx | null; + authorized: "DENY"; + policiesFailed: Array; }; export type OperationPoliciesConfig = { timer_policy_eval_retries: number; }; +interface PolicyForStage { + /** Field name according to the underlying struct on the typegraph */ + canonFieldName: string; + /** Each item is either a PolicyIndicesByEffect or a number */ + indices: Array; +} + +// This is arbitrary, but must be unique in such a way that the user produced +// stages do not share the same id. +const EXPOSE_STAGE_ID = ""; + export class OperationPolicies { - // should be private -- but would not be testable - functions: Map; - private policyLists: Map; - private resolvers: Map; + #stageToPolicies: Map> = new Map(); + #resolvers: Map = new Map(); constructor( private tg: TypeGraph, - builder: OperationPoliciesBuilder, - config: OperationPoliciesConfig, + private orderedStageMetadata: Array, + private config: OperationPoliciesConfig, ) { - this.functions = builder.subtrees; + this.#prepareStageToPolicies(); + this.#preallocateResolvers(); + } - this.policyLists = new Map(); - for (const [stageId, subtree] of this.functions.entries()) { - const { funcTypeIdx, topLevel, referencedTypes } = subtree; - ensure( - referencedTypes.has(stageId) && - referencedTypes.get(stageId)!.includes(funcTypeIdx), - "unexpected", - ); - for (const types of referencedTypes.values()) { - // set policyLists - for (const typeIdx of types) { - if (this.policyLists.has(typeIdx)) { - continue; - } - const policies = this.tg.type(typeIdx).policies; - if (policies.length > 0) { - this.policyLists.set(typeIdx, policies); - } - } - } + #prepareStageToPolicies() { + this.#stageToPolicies = new Map(); + + // Note: policies for exposed functions are hoisted on the root struct (index 0) + // If a function has a policy it overrides the definition on the root + const exposePolicies = this.#getPolicies(0); + const funcWithPolicies = exposePolicies + .filter(({ indices }) => indices.length > 0) + .map(({ canonFieldName }) => canonFieldName); + + this.#stageToPolicies.set(EXPOSE_STAGE_ID, exposePolicies); + + for ( + const { stageId, typeIdx: maybeWrappedIdx, node } of this + .orderedStageMetadata + ) { + const policies = this.#getPolicies(maybeWrappedIdx); + this.#stageToPolicies.set(stageId, policies); + // console.log("> found", stageId, policies, node, this.#findSelectedFields(stageId)); // top-level functions must have policies - if (topLevel && !this.policyLists.has(funcTypeIdx)) { + const isTopLevel = stageId.split(".").length == 1; + if (isTopLevel && !funcWithPolicies.includes(node)) { const details = [ - `top-level function '${this.tg.type(funcTypeIdx).title}'`, + `top-level function '${this.tg.type(maybeWrappedIdx).title}'`, `at '${stageId}'`, ].join(" "); throw new Error( @@ -86,264 +113,349 @@ export class OperationPolicies { ); } } + } + + #preallocateResolvers() { + this.#resolvers = new Map(); + const policyIndicesWithDup = Array.from(this.#stageToPolicies.values()) + .map((policyPerName) => { + const indices = policyPerName.map(({ indices }) => indices); + return indices.flat(); + }) + .flat(); + + const policyIndices = new Set(policyIndicesWithDup); + + for (const indicesData of policyIndices) { + let toPrepare = [] as Array; + if (typeof indicesData == "number") { + toPrepare = [indicesData]; + } else { + toPrepare = Object.values(indicesData); + } - this.resolvers = new Map(); - const policies = new Set([...this.policyLists.values()].flat()); - for (const idx of policies) { - for (const polIdx of iterIndices(idx)) { + for (const polIdx of toPrepare) { const mat = this.tg.policyMaterializer(this.tg.policy(polIdx)); const runtime = this.tg.runtimeReferences[mat.runtime] as DenoRuntime; ensure( runtime.constructor === DenoRuntime, "Policies must run on a Deno Runtime", ); - if (!this.resolvers.has(polIdx)) { - this.resolvers.set( + if (!this.#resolvers.has(polIdx)) { + this.#resolvers.set( polIdx, - runtime.delegate(mat, false, config.timer_policy_eval_retries), + runtime.delegate(mat, false, this.config.timer_policy_eval_retries), ); } } } } + /** + * Evaluate each stage policy in traversal order + * + * - `PASS`: continue further, same as no policies + * - `ALLOW`: stops evaluation for parent, and skip any child stage + * - `DENY`: throw an error and stopping everything + */ public async authorize(context: Context, info: Info, verbose: boolean) { const logger = getLogger("policies"); - const authorizedTypes: Record> = { - read: new Set(), - create: new Set(), - update: new Set(), - delete: new Set(), + + const outputCache = new Map(); + const getResolverResult = this.#createPolicyEvaluator( + { context, info }, + outputCache, + verbose, + logger, + ); + + const resolvedPolicyCachePerStage: Map = + new Map(); + + const fakeStageMeta = { + isTopLevel: true, + stageId: EXPOSE_STAGE_ID, + typeIdx: 0, + } as StageMetadata; + const stageMetaList = [fakeStageMeta, ...this.orderedStageMetadata]; + + let activeEffect = this.#getEffectOrNull(fakeStageMeta.typeIdx) ?? "read"; // root + + traversal: for (const { stageId, typeIdx } of stageMetaList) { + const newEffect = this.#getEffectOrNull(typeIdx); + if (newEffect != null) { + activeEffect = newEffect; + } + + for ( + const [priorStageId, verdict] of resolvedPolicyCachePerStage.entries() + ) { + const globalAllows = priorStageId == EXPOSE_STAGE_ID && + verdict == "ALLOW"; + if (globalAllows) { + break traversal; + } + + const parentAllows = stageId.startsWith(priorStageId) && + verdict == "ALLOW"; + if (parentAllows) { + continue traversal; + } // elif deny => already thrown + } + + const res = await this.#checkStageAuthorization( + stageId, + activeEffect, + getResolverResult, + ); + + switch (res.authorized) { + case "ALLOW": { + resolvedPolicyCachePerStage.set(stageId, "ALLOW"); + continue; + } + case "PASS": { + resolvedPolicyCachePerStage.set(stageId, "PASS"); + continue; + } + default: { + resolvedPolicyCachePerStage.set(stageId, res.authorized); + const policyNames = res.policiesFailed.map((operand) => ({ + name: this.tg.policy(operand.index).name, + concernedField: operand.canonFieldName, + })); + + throw new BadContext( + this.#getRejectionReason(stageId, activeEffect, policyNames), + ); + } + } + } + } + + #getRejectionReason( + stageId: StageId, + effect: EffectType, + policiesData: Array<{ name: string; concernedField: string }>, + ): string { + const getPath = (concernedField: string) => { + if (stageId == EXPOSE_STAGE_ID) { + return [EXPOSE_STAGE_ID, concernedField].join("."); + } + return [EXPOSE_STAGE_ID, stageId, concernedField].join("."); }; - const cache = new Map(); + const detailsPerPolicy = policiesData + .map(({ name, concernedField }) => + [ + `policy '${name}'`, + `with effect '${effect}'`, + `at '${getPath(concernedField)}'`, + ].join(" ") + ); + return `Authorization failed for ${detailsPerPolicy.join("; ")}`; + } - const getResolverResult = async ( - idx: PolicyIdx, + #createPolicyEvaluator( + partialResolverInput: { context: Context; info: Info }, + outputCache: Map, + verbose: boolean, + logger: Logger, + ): GetResolverResult { + return async ( + polIdx: PolicyIdx, effect: EffectType, - ): Promise => { + ): Promise => { verbose && logger.info( `checking policy '${ - this.tg.policy(idx).name - }'[${idx}] with effect '${effect}'...`, + this.tg.policy(polIdx).name + }'[${polIdx}] with effect '${effect}'...`, ); - if (cache.has(idx)) { - return cache.get(idx) as boolean | null; + if (outputCache.has(polIdx)) { + return outputCache.get(polIdx); } - const resolver = this.resolvers.get(idx); + const resolver = this.#resolvers.get(polIdx); ensure( resolver != null, `Could not find resolver for the policy '${ - this.tg.policy(idx).name + this.tg.policy(polIdx).name }'; effect=${effect}`, ); const res = (await resolver!({ _: { parent: {}, - context, - info, + context: partialResolverInput.context, + info: partialResolverInput.info, variables: {}, effect: effect === "read" ? null : effect, }, - })) as boolean | null; - cache.set(idx, res); + })) as PolicyResolverOutput; + outputCache.set(polIdx, res); verbose && logger.info(`> authorize: ${res}`); return res; }; + } - // TODO refactor: too much indentation - for (const [_stageId, subtree] of this.functions) { - const effect = this.tg.materializer( - this.tg.type(subtree.funcTypeIdx, Type.FUNCTION).materializer, - ).effect.effect ?? "read"; - - this.authorizeArgs( - subtree.argPolicies, - effect, - getResolverResult, - authorizedTypes[effect], - ); - - for (const [stageId, types] of subtree.referencedTypes) { - for (const typeIdx of types) { - if (authorizedTypes[effect].has(typeIdx)) { - continue; - } - const policies = (this.policyLists.get(typeIdx) ?? []).map((p) => - typeof p === "number" ? p : p[effect] ?? null - ); - - if (policies.some((idx) => idx == null)) { - throw new BadContext( - this.getRejectionReason(stageId, typeIdx, effect, "__deny"), - ); - } - - const res = await this.checkTypePolicies( - policies as number[], - effect, - getResolverResult, - ); - - if (res.authorized) { - continue; - } - - if (res.policyIdx == null) { - const typ = this.tg.type(typeIdx); - throw new Error( - `No policy took decision on type '${typ.title}' ('${typ.type}') at '.${stageId}'`, - ); - } - - const policyName = this.tg.policy(res.policyIdx).name; - throw new BadContext( - this.getRejectionReason(stageId, typeIdx, effect, policyName), + /** + * A single type may hold multiple policies + * + * - `ALLOW`: ALLOW & P = P + * - `DENY`: DENY & P = DENY + * + * DENY and ALLOW combine just like booleans with the AND gate + * + * PASS does not participate. + */ + async #composePolicies( + policies: Array, + effect: EffectType, + getResolverResult: GetResolverResult, + ): Promise { + const operands = []; + const deniersIdx = []; + for (const policyOperand of policies) { + const res = await getResolverResult(policyOperand.index, effect); + + switch (res) { + case "ALLOW": { + operands.push(true); + break; + } + case "DENY": { + // We can fail fast here with a throw + // but we can assume policies are reused accross + // types (so high cache hit) + operands.push(false); + deniersIdx.push(policyOperand); + break; + } + case "PASS": { + continue; + } + default: { + throw new Error( + `Could not take decision on value: ${ + JSON.stringify(res) + }, policy must return either "ALLOW", "DENY" or "PASS"`, ); } } } - } - getRejectionReason( - stageId: StageId, - typeIdx: TypeIdx, - effect: EffectType, - policyName: string, - ): string { - const typ = this.tg.type(typeIdx); - const details = [ - `policy '${policyName}'`, - `with effect '${effect}'`, - `on type '${typ.title}' ('${typ.type}')`, - `at '.${stageId}'`, - ].join(" "); - return `Authorization failed for ${details}`; + if (operands.length == 0) { + return { authorized: "PASS" }; + } else { + if (operands.every((_bool) => _bool)) { + return { authorized: "ALLOW" }; + } else { + return { authorized: "DENY", policiesFailed: deniersIdx }; + } + } } - private async authorizeArgs( - argPolicies: ArgPolicies, - effect: EffectType, - getResolverResult: GetResolverResult, - authorizedTypes: Set, - ) { - for (const [typeIdx, { argDetails, policyIndices }] of argPolicies) { - if (authorizedTypes.has(typeIdx)) { - continue; - } - authorizedTypes.add(typeIdx); + #getPolicies(typeIdx: number): Array { + let nestedTypeIdx = typeIdx; + let nestedSchema = this.tg.type(nestedTypeIdx); - const res = await this.checkTypePolicies( - policyIndices, - effect, - getResolverResult, - ); - if (!res.authorized) { - // unauthorized or no decision - throw new Error(`Unexpected argument ${argDetails}`); - } + if (isFunction(nestedSchema)) { + nestedTypeIdx = nestedSchema.output; + nestedSchema = this.tg.type(nestedTypeIdx); } - } - private async checkTypePolicies( - policies: PolicyIdx[], - effect: EffectType, - getResolverResult: GetResolverResult, - ): Promise { - if (policies.length === 0) { - return { authorized: true }; + while (isQuantifier(nestedSchema)) { + nestedTypeIdx = getWrappedType(nestedSchema); + nestedSchema = this.tg.type(nestedTypeIdx); } - for (const polIdx of policies) { - const res = await getResolverResult(polIdx, effect); - if (res == null) { - continue; - } - if (res) { - return { authorized: true }; - } + if (isEither(nestedSchema) || isUnion(nestedSchema)) { + const variantIndices = isEither(nestedSchema) + ? nestedSchema.oneOf + : nestedSchema.anyOf; + const policies = variantIndices.map((idx) => this.#getPolicies(idx)) + .flat(); + return policies; + } - return { authorized: false, policyIdx: polIdx }; + let out: Record> = {}; + if (nestedSchema.type == "object") { + out = nestedSchema.policies ?? {}; } - return { authorized: false, policyIdx: null }; + return Object.entries(out).map(([k, v]) => ({ + canonFieldName: k, + indices: v, + })); } -} -interface SubtreeData { - stageId: StageId; - funcTypeIdx: TypeIdx; - argPolicies: ArgPolicies; - topLevel: boolean; - referencedTypes: Map; -} - -export class OperationPoliciesBuilder { - // stack of function stages - stack: SubtreeData[] = []; - subtrees: Map = new Map(); - current: SubtreeData | null = null; + #getEffectOrNull(typeIdx: number) { + let effect = null; + const node = this.tg.type(typeIdx); + if (isFunction(node)) { + const matIdx = this.tg.type(typeIdx, Type.FUNCTION).materializer; + effect = this.tg.materializer(matIdx).effect.effect ?? effect; + } - constructor( - private tg: TypeGraph, - private config: OperationPoliciesConfig, - ) {} - - // set current function stage - push(stageId: StageId, funcTypeIdx: TypeIdx, argPolicies: ArgPolicies) { - const subtreeData = { - stageId, - funcTypeIdx, - argPolicies, - topLevel: this.stack.length === 0, - referencedTypes: new Map(), - }; - this.current = subtreeData; - this.stack.push(subtreeData); - this.subtrees.set(stageId, subtreeData); + return effect; } - // set current function stage to parent function stage - pop(stageId: StageId) { - ensure(this.stack.pop()!.stageId === stageId, "unexpected: invalid state"); - const top = this.stack.pop(); - if (top == null) { - this.current == null; - } else { - this.stack.push(top); - this.current = top; - } - } + async #checkStageAuthorization( + stageId: string, + effect: EffectType, + getResolverResult: GetResolverResult, + ) { + const selectedFields = this.#findSelectedFields(stageId); - #isNamespace(typeIdx: TypeIdx): boolean { - return this.tg.tg.meta.namespaces!.includes(typeIdx); - } + const policiesForStage = this.#stageToPolicies.get(stageId) ?? []; + const policies = []; + for (const { canonFieldName, indices } of policiesForStage) { + // Note: canonFieldName is the field on the type (but not the alias if any!) + if (!selectedFields.includes(canonFieldName)) { + continue; + } - setReferencedTypes(stageId: StageId, ...types: TypeIdx[]): TypeIdx[] { - if (this.current == null) { - if (this.tg.tg.meta.namespaces!.includes(types[0])) { - return types; + for (const index of indices) { + if (typeof index == "number") { + policies.push({ canonFieldName, index }); + } else { + const actualIndex = index[effect] ?? null; + if (actualIndex == null) { + throw new BadContext( + this.#getRejectionReason(stageId, effect, [{ + name: "__deny", + concernedField: canonFieldName, + }]), + ); + } + + policies.push({ canonFieldName, index: actualIndex }); + } } - throw new Error("unexpected state"); } - this.current.referencedTypes.set(stageId, types); - return types; + + return await this.#composePolicies( + policies, + effect, + getResolverResult, + ); } - build(): OperationPolicies { - return new OperationPolicies(this.tg, this, this.config); + #findSelectedFields(targetStageId: string) { + return this.orderedStageMetadata.map(({ stageId, node }) => { + const chunks = stageId.split("."); + const parent = chunks.slice(0, -1).join("."); + if (parent == "" && targetStageId == EXPOSE_STAGE_ID) { + return node; + } + + return targetStageId == parent ? node : null; + }).filter((name) => name != null); } -} -function* iterIndices(indices: PolicyIndices): IterableIterator { - if (typeof indices === "number") { - yield indices; - } else { - for (const idx of Object.values(indices) as number[]) { - yield idx; - } + // for testing + get stageToPolicies() { + return this.#stageToPolicies; } } diff --git a/src/typegate/src/engine/typecheck/result.ts b/src/typegate/src/engine/typecheck/result.ts index 3cae058621..b76f753246 100644 --- a/src/typegate/src/engine/typecheck/result.ts +++ b/src/typegate/src/engine/typecheck/result.ts @@ -108,7 +108,6 @@ export class ResultValidationCompiler { cg.generateStringValidator({ type: "string", title: "__TypeName", - policies: [], }); } else if (isScalar(typeNode)) { if (entry.selectionSet != null) { diff --git a/src/typegate/src/runtimes/deno/deno.ts b/src/typegate/src/runtimes/deno/deno.ts index d05392b5e4..11989285f3 100644 --- a/src/typegate/src/runtimes/deno/deno.ts +++ b/src/typegate/src/runtimes/deno/deno.ts @@ -23,6 +23,7 @@ import type { Task } from "./shared_types.ts"; import { path } from "compress/deps.ts"; import { globalConfig as config } from "../../config.ts"; import { createArtifactMeta } from "../utils/deno.ts"; +import { PolicyResolverOutput } from "../../engine/planner/policies.ts"; import { getInjectionValues } from "../../engine/planner/injection_utils.ts"; import DynamicInjection from "../../engine/injection/dynamic.ts"; import { getLogger } from "../../log.ts"; @@ -33,7 +34,10 @@ const predefinedFuncs: Record>> = { identity: ({ _, ...args }) => args, true: () => true, false: () => false, - internal_policy: ({ _: { context } }) => context.provider === "internal", + allow: () => "ALLOW" as PolicyResolverOutput, + deny: () => "DENY" as PolicyResolverOutput, + pass: () => "PASS" as PolicyResolverOutput, + internal_policy: ({ _: { context } }) => context.provider === "internal" ? "ALLOW" : "DENY" as PolicyResolverOutput, }; export class DenoRuntime extends Runtime { diff --git a/src/typegate/src/runtimes/typegate.ts b/src/typegate/src/runtimes/typegate.ts index bec4fea41a..8d1a08703c 100644 --- a/src/typegate/src/runtimes/typegate.ts +++ b/src/typegate/src/runtimes/typegate.ts @@ -554,6 +554,33 @@ function walkPath( ); node = resNode; + const getPolicies = (node: TypeNode) => { + const fieldToPolicies = node.type == "object" + ? Object.entries(node.policies ?? []) + : []; + const ret = []; + for (const [fieldName, indices] of fieldToPolicies) { + const fmtedIndices = indices.map((index) => { + if (typeof index === "number") { + return tg.policy(index).name; + } + + return mapValues(index as Record, (value: number) => { + if (value === null) { + return null; + } + return tg.policy(value).name; + }); + }); + + ret.push( + { fieldName, policies: JSON.stringify(fmtedIndices) }, + ); + } + + return ret; + }; + return { optional: isOptional, title: node.title, @@ -563,18 +590,6 @@ function walkPath( format: format ?? null, fields: node.type == "object" ? collectObjectFields(tg, parent) : null, // TODO enum type on typegraph typegate.py - policies: node.policies.map((policy) => { - if (typeof policy === "number") { - return JSON.stringify(tg.policy(policy).name); - } - return JSON.stringify( - mapValues(policy as Record, (value: number) => { - if (value === null) { - return null; - } - return tg.policy(value).name; - }), - ); - }), + policies: getPolicies(node), }; } diff --git a/src/typegate/src/runtimes/typegraph.ts b/src/typegate/src/runtimes/typegraph.ts index aa4d8cf83f..b9b14c985a 100644 --- a/src/typegate/src/runtimes/typegraph.ts +++ b/src/typegate/src/runtimes/typegraph.ts @@ -58,6 +58,12 @@ function generateCustomScalar(type: TypeNode, idx: number) { throw `"${type.title}" of type "${type.type}" is not a scalar`; } +type FieldInfo = { + name: string; + typeIdx: number; + policies: PolicyIndices[]; +}; + export class TypeGraphRuntime extends Runtime { tg: TypeGraphDS; private scalarIndex = new Map(); @@ -400,7 +406,13 @@ export class TypeGraphRuntime extends Runtime { name: () => `${type.title}Inp`, description: () => `${type.title} input type`, inputFields: () => { - return Object.entries(type.properties).map(this.formatField(true)); + return Object.entries(type.properties).map(([name, typeIdx]) => + this.formatField(true)({ + name, + typeIdx, + policies: type.policies?.[name] ?? [], + }) + ); }, interfaces: () => [], }; @@ -413,7 +425,13 @@ export class TypeGraphRuntime extends Runtime { fields: () => { let entries = Object.entries(type.properties); entries = entries.sort((a, b) => b[1] - a[1]); - return entries.map(this.formatField(false)); + return entries.map(([name, typeIdx]) => + this.formatField(false)({ + name, + typeIdx, + policies: type.policies?.[name] ?? [], + }) + ); }, interfaces: () => [], }; @@ -477,7 +495,7 @@ export class TypeGraphRuntime extends Runtime { // enum: enumValues }; - policyDescription(type: TypeNode): string { + policyDescription(policies: PolicyIndices[]): string { const describeOne = (p: number) => this.tg.policies[p].name; const describe = (p: PolicyIndices) => { if (typeof p === "number") { @@ -487,12 +505,12 @@ export class TypeGraphRuntime extends Runtime { .map(([eff, polIdx]) => `${eff}:${describeOne(polIdx)}`) .join("; "); }; - const policies = type.policies.map(describe); + const policyNames = policies.map(describe); let ret = "\n\nPolicies:\n"; - if (policies.length > 0) { - ret += policies.map((p: string) => `- ${p}`).join("\n"); + if (policyNames.length > 0) { + ret += policyNames.map((p: string) => `- ${p}`).join("\n"); } else { ret += "- inherit"; } @@ -500,51 +518,52 @@ export class TypeGraphRuntime extends Runtime { return ret; } - formatField = (asInput: boolean) => ([name, typeIdx]: [string, number]) => { - const type = this.tg.types[typeIdx]; - const common = { - // https://github.com/graphql/graphql-js/blob/main/src/type/introspection.ts#L329 - name: () => name, - description: () => `${name} field${this.policyDescription(type)}`, - isDeprecated: () => false, - deprecationReason: () => null, - }; + formatField = + (asInput: boolean) => ({ name, typeIdx, policies }: FieldInfo) => { + const type = this.tg.types[typeIdx]; + const common = { + // https://github.com/graphql/graphql-js/blob/main/src/type/introspection.ts#L329 + name: () => name, + description: () => `${name} field${this.policyDescription(policies)}`, + isDeprecated: () => false, + deprecationReason: () => null, + }; + + if (isFunction(type)) { + return { + ...common, + args: (_: DeprecatedArg = {}) => { + const inp = this.tg.types[type.input as number]; + ensure( + isObject(inp), + `${type} cannot be an input field, require struct`, + ); + let entries = Object.entries((inp as ObjectNode).properties); + entries = entries.sort((a, b) => b[1] - a[1]); + return entries + .map((entry) => + this.formatInputFields( + entry, + (type.injections ?? {})[entry[0]] ?? null, + ) + ) + .filter((f) => f !== null); + }, + type: () => { + const output = this.tg.types[type.output as number]; + return this.formatType(output, true, false); + }, + }; + } - if (isFunction(type)) { return { ...common, - args: (_: DeprecatedArg = {}) => { - const inp = this.tg.types[type.input as number]; - ensure( - isObject(inp), - `${type} cannot be an input field, require struct`, - ); - let entries = Object.entries((inp as ObjectNode).properties); - entries = entries.sort((a, b) => b[1] - a[1]); - return entries - .map((entry) => - this.formatInputFields( - entry, - (type.injections ?? {})[entry[0]] ?? null, - ) - ) - .filter((f) => f !== null); - }, + args: () => [], type: () => { - const output = this.tg.types[type.output as number]; - return this.formatType(output, true, false); + return this.formatType(type, true, asInput); }, }; - } - - return { - ...common, - args: () => [], - type: () => { - return this.formatType(type, true, asInput); - }, }; - }; } function emptyObjectScalar() { diff --git a/src/typegate/src/transports/graphql/typegraph.ts b/src/typegate/src/transports/graphql/typegraph.ts index 9c2f1ba890..b504a6cb95 100644 --- a/src/typegate/src/transports/graphql/typegraph.ts +++ b/src/typegate/src/transports/graphql/typegraph.ts @@ -3,19 +3,33 @@ import type { TypeGraphDS } from "../../typegraph/mod.ts"; import type { ObjectNode } from "../../typegraph/type_node.ts"; +import { PolicyIndices } from "../../typegraph/types.ts"; import { addNode } from "./utils.ts"; type PropertiesTable = Record; +type SplitResult = { + queries: { + properties: PropertiesTable; + policies: Record; + }; + mutations: { + properties: PropertiesTable; + policies: Record; + }; +}; + /** * Splits a TypeGraph into GraphQL `queries` and `mutations` */ function splitGraphQLOperations( typegraph: TypeGraphDS, node: ObjectNode, -): [PropertiesTable, PropertiesTable] { - const queryProperties: PropertiesTable = {}; - const mutationProperties: PropertiesTable = {}; +): SplitResult { + const res: SplitResult = { + queries: { properties: {}, policies: {} }, + mutations: { properties: {}, policies: {} }, + }; if (typegraph.meta.namespaces == null) { typegraph.meta.namespaces = []; @@ -25,37 +39,38 @@ function splitGraphQLOperations( for (const [propertyName, typeIndex] of Object.entries(node.properties)) { const childNode = typegraph.types[typeIndex]; - // if the leaf node of a path its a function - // with a materializer that has an effect, + // if the leaf node of a path is a function + // with a materializer that has an effect other than `read`, // classify the root node of this path as a `mutation` // otherwise as a `query` switch (childNode.type) { case "object": { - const [childQueryProperties, childMutationProperties] = - splitGraphQLOperations( - typegraph, - childNode, - ); - - if (Object.keys(childQueryProperties).length === 0) { - mutationProperties[propertyName] = typeIndex; + const child = splitGraphQLOperations( + typegraph, + childNode, + ); + + if (Object.keys(child.queries.properties).length === 0) { + res.mutations.properties[propertyName] = typeIndex; namespaces.push(typeIndex); - } else if (Object.keys(childMutationProperties).length === 0) { - queryProperties[propertyName] = typeIndex; + } else if (Object.keys(child.mutations.properties).length === 0) { + res.queries.properties[propertyName] = typeIndex; namespaces.push(typeIndex); } else { - queryProperties[propertyName] = addNode(typegraph, { + res.queries.properties[propertyName] = addNode(typegraph, { ...node, title: `${node.title}_q`, - properties: childQueryProperties, + properties: child.queries.properties, + policies: child.queries.policies, }); - namespaces.push(queryProperties[propertyName]); - mutationProperties[propertyName] = addNode(typegraph, { + namespaces.push(res.queries.properties[propertyName]); + res.mutations.properties[propertyName] = addNode(typegraph, { ...node, title: `${node.title}_m`, - properties: childMutationProperties, + properties: child.mutations.properties, + policies: child.mutations.policies, }); - namespaces.push(mutationProperties[propertyName]); + namespaces.push(res.mutations.properties[propertyName]); } break; } @@ -69,10 +84,16 @@ function splitGraphQLOperations( childMaterializer.effect.effect === null || childMaterializer.effect.effect === "read" ) { - queryProperties[propertyName] = typeIndex; + res.queries.properties[propertyName] = typeIndex; + if (propertyName in (node.policies ?? {})) { + res.queries.policies[propertyName] = node.policies![propertyName]; + } // TODO additional checks } else { - mutationProperties[propertyName] = typeIndex; + res.mutations.properties[propertyName] = typeIndex; + if (propertyName in (node.policies ?? {})) { + res.mutations.policies[propertyName] = node.policies![propertyName]; + } // TODO additional checks } @@ -81,7 +102,7 @@ function splitGraphQLOperations( } } - return [queryProperties, mutationProperties]; + return res; } export function parseGraphQLTypeGraph(tgOrig: TypeGraphDS): TypeGraphDS { @@ -94,7 +115,7 @@ export function parseGraphQLTypeGraph(tgOrig: TypeGraphDS): TypeGraphDS { types: [...tgOrig.types], }; - const [queryProperties, mutationProperties] = splitGraphQLOperations( + const { queries, mutations } = splitGraphQLOperations( typegraph, rootNode, ); @@ -106,16 +127,16 @@ export function parseGraphQLTypeGraph(tgOrig: TypeGraphDS): TypeGraphDS { const queryIndex = addNode(typegraph, { ...rootNode, title: "Query", - properties: queryProperties, + ...queries, }); typegraph.meta.namespaces!.push(queryIndex); rootNode.properties.query = queryIndex; - if (Object.keys(mutationProperties).length > 0) { + if (Object.keys(mutations.properties).length > 0) { const mutationIndex = addNode(typegraph, { ...rootNode, title: "Mutation", - properties: mutationProperties, + ...mutations, }); typegraph.meta.namespaces!.push(mutationIndex); rootNode.properties.mutation = mutationIndex; diff --git a/src/typegate/src/typegraph/mod.ts b/src/typegate/src/typegraph/mod.ts index 1a0156dcc6..dc093036a4 100644 --- a/src/typegate/src/typegraph/mod.ts +++ b/src/typegate/src/typegraph/mod.ts @@ -97,7 +97,6 @@ export class TypeGraph implements AsyncDisposable { static typenameType: TypeNode = { title: "string", type: "string", - policies: [], }; root: TypeNode; diff --git a/src/typegate/src/typegraph/types.ts b/src/typegate/src/typegraph/types.ts index 84cd5570a7..ce2d855bcb 100644 --- a/src/typegate/src/typegraph/types.ts +++ b/src/typegate/src/typegraph/types.ts @@ -6,7 +6,6 @@ export type OptionalNode = { type: "optional"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; item: number; @@ -15,14 +14,12 @@ export type OptionalNode = { export type BooleanNode = { type: "boolean"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; }; export type FloatNode = { type: "float"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; minimum?: number | null; @@ -34,7 +31,6 @@ export type FloatNode = { export type IntegerNode = { type: "integer"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; minimum?: number | null; @@ -46,7 +42,6 @@ export type IntegerNode = { export type StringNode = { type: "string"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; minLength?: number | null; @@ -57,7 +52,6 @@ export type StringNode = { export type FileNode = { type: "file"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; minSize?: number | null; @@ -67,7 +61,7 @@ export type FileNode = { export type ObjectNode = { type: "object"; title: string; - policies: PolicyIndices[]; + policies?: Record; description?: string | null; enum?: string[] | null; properties: { @@ -79,7 +73,6 @@ export type ObjectNode = { export type ListNode = { type: "list"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; items: number; @@ -93,7 +86,6 @@ export type InjectionNode = export type FunctionNode = { type: "function"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; input: number; @@ -109,7 +101,6 @@ export type FunctionNode = { export type UnionNode = { type: "union"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; anyOf: number[]; @@ -117,7 +108,6 @@ export type UnionNode = { export type EitherNode = { type: "either"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; oneOf: number[]; @@ -125,7 +115,6 @@ export type EitherNode = { export type AnyNode = { type: "any"; title: string; - policies: PolicyIndices[]; description?: string | null; enum?: string[] | null; }; diff --git a/src/typegate/src/typegraphs/introspection.json b/src/typegate/src/typegraphs/introspection.json index eeabf338f6..66947140e7 100644 --- a/src/typegate/src/typegraphs/introspection.json +++ b/src/typegate/src/typegraphs/introspection.json @@ -3,26 +3,29 @@ { "type": "object", "title": "introspection", - "policies": [], "properties": { "__type": 1, - "__schema": 45 + "__schema": 26 }, "id": [], "required": [ "__type", "__schema" - ] + ], + "policies": { + "__type": [ + 0 + ], + "__schema": [ + 0 + ] + } }, { "type": "function", "title": "root___type_fn", - "policies": [ - 0 - ], "input": 2, "output": 4, - "injections": {}, "runtimeConfig": null, "materializer": 0, "rate_weight": null, @@ -31,48 +34,58 @@ { "type": "object", "title": "root___type_fn_input", - "policies": [], "properties": { "name": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [] + } }, { "type": "string", - "title": "root___type_fn_input_name_string", - "policies": [] + "title": "root___type_fn_input_name_string" }, { "type": "optional", "title": "root___type_fn_output", - "policies": [], "item": 5, "default_value": null }, { "type": "object", "title": "type", - "policies": [], "properties": { "kind": 6, "name": 7, - "description": 8, - "specifiedByURL": 9, - "fields": 10, - "interfaces": 27, - "possibleTypes": 29, - "enumValues": 31, - "inputFields": 39, - "ofType": 44 + "description": 7, + "specifiedByURL": 7, + "fields": 8, + "interfaces": 18, + "possibleTypes": 18, + "enumValues": 20, + "inputFields": 24, + "ofType": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "kind": [], + "name": [], + "description": [], + "specifiedByURL": [], + "fields": [], + "interfaces": [], + "possibleTypes": [], + "enumValues": [], + "inputFields": [], + "ofType": [] + } }, { "type": "string", "title": "type_kind", - "policies": [], "enum": [ "\"SCALAR\"", "\"OBJECT\"", @@ -87,31 +100,14 @@ { "type": "optional", "title": "type_name_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "type_description_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "type_specifiedByURL_root___type_fn_input_name_string_optional", - "policies": [], "item": 3, "default_value": null }, { "type": "function", "title": "type_fields_fn", - "policies": [], - "input": 11, - "output": 14, - "injections": {}, + "input": 9, + "output": 12, "runtimeConfig": null, "materializer": 1, "rate_weight": null, @@ -120,291 +116,166 @@ { "type": "object", "title": "type_fields_fn_input", - "policies": [], "properties": { - "includeDeprecated": 12 + "includeDeprecated": 10 }, "id": [], - "required": [] + "required": [], + "policies": { + "includeDeprecated": [] + } }, { "type": "optional", "title": "type_fields_fn_input_includeDeprecated_type_fields_fn_input_includeDeprecated_boolean_optional", - "policies": [], - "item": 13, + "item": 11, "default_value": null }, { "type": "boolean", - "title": "type_fields_fn_input_includeDeprecated_boolean", - "policies": [] + "title": "type_fields_fn_input_includeDeprecated_boolean" }, { "type": "optional", "title": "type_fields_fn_output", - "policies": [], - "item": 15, + "item": 13, "default_value": null }, { "type": "list", "title": "type_fields_fn_output", - "policies": [], - "items": 16 + "items": 14 }, { "type": "object", "title": "field", - "policies": [], "properties": { "name": 3, - "description": 17, - "args": 18, + "description": 7, + "args": 15, "type": 5, - "isDeprecated": 13, - "deprecationReason": 26 + "isDeprecated": 11, + "deprecationReason": 7 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "field_description_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "name": [], + "description": [], + "args": [], + "type": [], + "isDeprecated": [], + "deprecationReason": [] + } }, { "type": "function", "title": "field_args_fn", - "policies": [], - "input": 19, - "output": 21, - "injections": {}, + "input": 9, + "output": 16, "runtimeConfig": null, "materializer": 1, "rate_weight": null, "rate_calls": false }, - { - "type": "object", - "title": "field_args_fn_input", - "policies": [], - "properties": { - "includeDeprecated": 20 - }, - "id": [], - "required": [] - }, - { - "type": "optional", - "title": "field_args_fn_input_includeDeprecated_type_fields_fn_input_includeDeprecated_boolean_optional", - "policies": [], - "item": 13, - "default_value": null - }, { "type": "list", "title": "field_args_fn_output", - "policies": [], - "items": 22 + "items": 17 }, { "type": "object", "title": "input_value", - "policies": [], "properties": { "name": 3, - "description": 23, + "description": 7, "type": 5, - "defaultValue": 24, - "isDeprecated": 13, - "deprecationReason": 25 + "defaultValue": 7, + "isDeprecated": 11, + "deprecationReason": 7 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "input_value_description_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "input_value_defaultValue_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "input_value_deprecationReason_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "field_deprecationReason_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "name": [], + "description": [], + "type": [], + "defaultValue": [], + "isDeprecated": [], + "deprecationReason": [] + } }, { "type": "optional", "title": "type_interfaces_type_interfaces_type_list_optional", - "policies": [], - "item": 28, + "item": 19, "default_value": null }, { "type": "list", "title": "type_interfaces_type_list", - "policies": [], - "items": 5 - }, - { - "type": "optional", - "title": "type_possibleTypes_type_possibleTypes_type_list_optional", - "policies": [], - "item": 30, - "default_value": null - }, - { - "type": "list", - "title": "type_possibleTypes_type_list", - "policies": [], "items": 5 }, { "type": "function", "title": "type_enumValues_fn", - "policies": [], - "input": 32, - "output": 34, - "injections": {}, + "input": 9, + "output": 21, "runtimeConfig": null, "materializer": 1, "rate_weight": null, "rate_calls": false }, - { - "type": "object", - "title": "type_enumValues_fn_input", - "policies": [], - "properties": { - "includeDeprecated": 33 - }, - "id": [], - "required": [] - }, - { - "type": "optional", - "title": "type_enumValues_fn_input_includeDeprecated_type_fields_fn_input_includeDeprecated_boolean_optional", - "policies": [], - "item": 13, - "default_value": null - }, { "type": "optional", "title": "type_enumValues_fn_output", - "policies": [], - "item": 35, + "item": 22, "default_value": null }, { "type": "list", "title": "type_enumValues_fn_output", - "policies": [], - "items": 36 + "items": 23 }, { "type": "object", "title": "enum_value", - "policies": [], "properties": { "name": 3, - "description": 37, - "isDeprecated": 13, - "deprecationReason": 38 + "description": 7, + "isDeprecated": 11, + "deprecationReason": 7 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "enum_value_description_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "enum_value_deprecationReason_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "name": [], + "description": [], + "isDeprecated": [], + "deprecationReason": [] + } }, { "type": "function", "title": "type_inputFields_fn", - "policies": [], - "input": 40, - "output": 42, - "injections": {}, + "input": 9, + "output": 25, "runtimeConfig": null, "materializer": 1, "rate_weight": null, "rate_calls": false }, - { - "type": "object", - "title": "type_inputFields_fn_input", - "policies": [], - "properties": { - "includeDeprecated": 41 - }, - "id": [], - "required": [] - }, - { - "type": "optional", - "title": "type_inputFields_fn_input_includeDeprecated_type_fields_fn_input_includeDeprecated_boolean_optional", - "policies": [], - "item": 13, - "default_value": null - }, { "type": "optional", "title": "type_inputFields_fn_output", - "policies": [], - "item": 43, - "default_value": null - }, - { - "type": "list", - "title": "type_inputFields_fn_output", - "policies": [], - "items": 22 - }, - { - "type": "optional", - "title": "type_ofType_type_optional", - "policies": [], - "item": 5, + "item": 16, "default_value": null }, { "type": "function", "title": "root___schema_fn", - "policies": [ - 0 - ], - "input": 46, - "output": 47, - "injections": {}, + "input": 27, + "output": 28, "runtimeConfig": null, "materializer": 3, "rate_weight": null, @@ -413,7 +284,6 @@ { "type": "object", "title": "root___schema_fn_input", - "policies": [], "properties": {}, "id": [], "required": [] @@ -421,82 +291,58 @@ { "type": "object", "title": "schema", - "policies": [], "properties": { - "description": 48, - "types": 49, + "description": 7, + "types": 19, "queryType": 5, - "mutationType": 50, - "subscriptionType": 51, - "directives": 52 + "mutationType": 4, + "subscriptionType": 4, + "directives": 29 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "schema_description_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "list", - "title": "schema_types_type_list", - "policies": [], - "items": 5 - }, - { - "type": "optional", - "title": "schema_mutationType_type_optional", - "policies": [], - "item": 5, - "default_value": null - }, - { - "type": "optional", - "title": "schema_subscriptionType_type_optional", - "policies": [], - "item": 5, - "default_value": null + "required": [], + "policies": { + "description": [], + "types": [], + "queryType": [], + "mutationType": [], + "subscriptionType": [], + "directives": [] + } }, { "type": "list", "title": "schema_directives_directive_list", - "policies": [], - "items": 53 + "items": 30 }, { "type": "object", "title": "directive", - "policies": [], "properties": { "name": 3, - "description": 54, - "isRepeatable": 13, - "locations": 55, - "args": 57 + "description": 7, + "isRepeatable": 11, + "locations": 31, + "args": 15 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "directive_description_root___type_fn_input_name_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "name": [], + "description": [], + "isRepeatable": [], + "locations": [], + "args": [] + } }, { "type": "list", "title": "directive_locations_directive_location_list", - "policies": [], - "items": 56 + "items": 32 }, { "type": "string", "title": "directive_location", - "policies": [], "enum": [ "\"QUERY\"", "\"MUTATION\"", @@ -518,41 +364,6 @@ "\"INPUT_OBJECT\"", "\"INPUT_FIELD_DEFINITION\"" ] - }, - { - "type": "function", - "title": "directive_args_fn", - "policies": [], - "input": 58, - "output": 60, - "injections": {}, - "runtimeConfig": null, - "materializer": 1, - "rate_weight": null, - "rate_calls": false - }, - { - "type": "object", - "title": "directive_args_fn_input", - "policies": [], - "properties": { - "includeDeprecated": 59 - }, - "id": [], - "required": [] - }, - { - "type": "optional", - "title": "directive_args_fn_input_includeDeprecated_type_fields_fn_input_includeDeprecated_boolean_optional", - "policies": [], - "item": 13, - "default_value": null - }, - { - "type": "list", - "title": "directive_args_fn_output", - "policies": [], - "items": 22 } ], "materializers": [ @@ -582,7 +393,7 @@ "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { @@ -635,4 +446,4 @@ "randomSeed": null, "artifacts": {} } -} +} \ No newline at end of file diff --git a/src/typegate/src/typegraphs/prisma_migration.json b/src/typegate/src/typegraphs/prisma_migration.json index 6d5d98040d..1a4da15dd4 100644 --- a/src/typegate/src/typegraphs/prisma_migration.json +++ b/src/typegate/src/typegraphs/prisma_migration.json @@ -3,13 +3,12 @@ { "type": "object", "title": "typegate/prisma_migration", - "policies": [], "properties": { "diff": 1, - "apply": 8, - "create": 14, - "deploy": 21, - "reset": 27 + "apply": 7, + "create": 11, + "deploy": 14, + "reset": 18 }, "id": [], "required": [ @@ -18,17 +17,30 @@ "create", "deploy", "reset" - ] + ], + "policies": { + "diff": [ + 0 + ], + "apply": [ + 0 + ], + "create": [ + 0 + ], + "deploy": [ + 0 + ], + "reset": [ + 0 + ] + } }, { "type": "function", "title": "root_diff_fn", - "policies": [ - 0 - ], "input": 2, "output": 6, - "injections": {}, "runtimeConfig": null, "materializer": 0, "rate_weight": null, @@ -37,59 +49,52 @@ { "type": "object", "title": "root_diff_fn_input", - "policies": [], "properties": { "typegraph": 3, "runtime": 4, "script": 5 }, "id": [], - "required": [] + "required": [], + "policies": { + "typegraph": [], + "runtime": [], + "script": [] + } }, { "type": "string", - "title": "root_diff_fn_input_typegraph_string", - "policies": [] + "title": "root_diff_fn_input_typegraph_string" }, { "type": "optional", "title": "root_diff_fn_input_runtime_root_diff_fn_input_typegraph_string_optional", - "policies": [], "item": 3, "default_value": null }, { "type": "boolean", - "title": "root_diff_fn_input_script_boolean", - "policies": [] + "title": "root_diff_fn_input_script_boolean" }, { "type": "object", "title": "root_diff_fn_output", - "policies": [], "properties": { - "diff": 7, + "diff": 4, "runtimeName": 3 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "root_diff_fn_output_diff_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "diff": [], + "runtimeName": [] + } }, { "type": "function", "title": "root_apply_fn", - "policies": [ - 0 - ], - "input": 9, - "output": 12, - "injections": {}, + "input": 8, + "output": 9, "runtimeConfig": null, "materializer": 2, "rate_weight": null, @@ -98,56 +103,45 @@ { "type": "object", "title": "root_apply_fn_input", - "policies": [], "properties": { "typegraph": 3, - "runtime": 10, - "migrations": 11, + "runtime": 4, + "migrations": 4, "resetDatabase": 5 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "root_apply_fn_input_runtime_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "root_apply_fn_input_migrations_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "typegraph": [], + "runtime": [], + "migrations": [], + "resetDatabase": [] + } }, { "type": "object", "title": "root_apply_fn_output", - "policies": [], "properties": { "databaseReset": 5, - "appliedMigrations": 13 + "appliedMigrations": 10 }, "id": [], - "required": [] + "required": [], + "policies": { + "databaseReset": [], + "appliedMigrations": [] + } }, { "type": "list", "title": "root_apply_fn_output_appliedMigrations_root_diff_fn_input_typegraph_string_list", - "policies": [], "items": 3 }, { "type": "function", "title": "root_create_fn", - "policies": [ - 0 - ], - "input": 15, - "output": 18, - "injections": {}, + "input": 12, + "output": 13, "runtimeConfig": null, "materializer": 3, "rate_weight": null, @@ -156,67 +150,46 @@ { "type": "object", "title": "root_create_fn_input", - "policies": [], "properties": { "typegraph": 3, - "runtime": 16, + "runtime": 4, "name": 3, "apply": 5, - "migrations": 17 + "migrations": 4 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "root_create_fn_input_runtime_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "root_create_fn_input_migrations_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "typegraph": [], + "runtime": [], + "name": [], + "apply": [], + "migrations": [] + } }, { "type": "object", "title": "root_create_fn_output", - "policies": [], "properties": { "createdMigrationName": 3, - "applyError": 19, - "migrations": 20, + "applyError": 4, + "migrations": 4, "runtimeName": 3 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "root_create_fn_output_applyError_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null - }, - { - "type": "optional", - "title": "root_create_fn_output_migrations_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "createdMigrationName": [], + "applyError": [], + "migrations": [], + "runtimeName": [] + } }, { "type": "function", "title": "root_deploy_fn", - "policies": [ - 0 - ], - "input": 22, - "output": 24, - "injections": {}, + "input": 15, + "output": 16, "runtimeConfig": null, "materializer": 4, "rate_weight": null, @@ -225,53 +198,42 @@ { "type": "object", "title": "root_deploy_fn_input", - "policies": [], "properties": { "typegraph": 3, - "runtime": 23, + "runtime": 4, "migrations": 3 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "root_deploy_fn_input_runtime_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "typegraph": [], + "runtime": [], + "migrations": [] + } }, { "type": "object", "title": "root_deploy_fn_output", - "policies": [], "properties": { - "migrationCount": 25, - "appliedMigrations": 26 + "migrationCount": 17, + "appliedMigrations": 10 }, "id": [], - "required": [] + "required": [], + "policies": { + "migrationCount": [], + "appliedMigrations": [] + } }, { "type": "integer", - "title": "root_deploy_fn_output_migrationCount_integer", - "policies": [] - }, - { - "type": "list", - "title": "root_deploy_fn_output_appliedMigrations_root_diff_fn_input_typegraph_string_list", - "policies": [], - "items": 3 + "title": "root_deploy_fn_output_migrationCount_integer" }, { "type": "function", "title": "root_reset_fn", - "policies": [ - 0 - ], - "input": 28, + "input": 19, "output": 5, - "injections": {}, "runtimeConfig": null, "materializer": 5, "rate_weight": null, @@ -280,20 +242,16 @@ { "type": "object", "title": "root_reset_fn_input", - "policies": [], "properties": { "typegraph": 3, - "runtime": 29 + "runtime": 4 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "root_reset_fn_input_runtime_root_diff_fn_input_typegraph_string_optional", - "policies": [], - "item": 3, - "default_value": null + "required": [], + "policies": { + "typegraph": [], + "runtime": [] + } } ], "materializers": [ @@ -314,7 +272,7 @@ "idempotent": true }, "data": { - "script": "var _my_lambda = (_args, { context }) => context.username === 'admin'", + "script": "var _my_lambda = (_args, { context }) => context.username === 'admin' ? 'ALLOW' : 'DENY'", "secrets": [] } }, @@ -411,4 +369,4 @@ "randomSeed": null, "artifacts": {} } -} +} \ No newline at end of file diff --git a/src/typegate/src/typegraphs/prisma_migration.py b/src/typegate/src/typegraphs/prisma_migration.py index ddc96df004..1e88ff7a65 100644 --- a/src/typegate/src/typegraphs/prisma_migration.py +++ b/src/typegate/src/typegraphs/prisma_migration.py @@ -26,7 +26,8 @@ def prisma_migration(g: Graph): deno = DenoRuntime() admin_only = deno.policy( - "admin_only", code="(_args, { context }) => context.username === 'admin'" + "admin_only", + code="(_args, { context }) => context.username === 'admin' ? 'ALLOW' : 'DENY'", ) g.auth(Auth.basic(["admin"])) diff --git a/src/typegate/src/typegraphs/typegate.json b/src/typegate/src/typegraphs/typegate.json index 391ec404f0..828df86a18 100644 --- a/src/typegate/src/typegraphs/typegate.json +++ b/src/typegate/src/typegraphs/typegate.json @@ -3,20 +3,19 @@ { "type": "object", "title": "typegate", - "policies": [], "properties": { "typegraphs": 1, "typegraph": 7, "addTypegraph": 12, "removeTypegraphs": 22, "argInfoByPath": 26, - "findAvailableOperations": 40, - "findPrismaModels": 48, - "execRawPrismaRead": 54, - "execRawPrismaCreate": 66, - "execRawPrismaUpdate": 67, - "execRawPrismaDelete": 68, - "queryPrismaModel": 69 + "findAvailableOperations": 39, + "findPrismaModels": 47, + "execRawPrismaRead": 53, + "execRawPrismaCreate": 64, + "execRawPrismaUpdate": 65, + "execRawPrismaDelete": 66, + "queryPrismaModel": 67 }, "id": [], "required": [ @@ -32,17 +31,51 @@ "execRawPrismaUpdate", "execRawPrismaDelete", "queryPrismaModel" - ] + ], + "policies": { + "typegraphs": [ + 0 + ], + "typegraph": [ + 0 + ], + "addTypegraph": [ + 0 + ], + "removeTypegraphs": [ + 0 + ], + "argInfoByPath": [ + 0 + ], + "findAvailableOperations": [ + 0 + ], + "findPrismaModels": [ + 0 + ], + "execRawPrismaRead": [ + 0 + ], + "execRawPrismaCreate": [ + 0 + ], + "execRawPrismaUpdate": [ + 0 + ], + "execRawPrismaDelete": [ + 0 + ], + "queryPrismaModel": [ + 0 + ] + } }, { "type": "function", "title": "root_typegraphs_fn", - "policies": [ - 0 - ], "input": 2, "output": 3, - "injections": {}, "runtimeConfig": null, "materializer": 0, "rate_weight": null, @@ -51,7 +84,6 @@ { "type": "object", "title": "root_typegraphs_fn_input", - "policies": [], "properties": {}, "id": [], "required": [] @@ -59,40 +91,36 @@ { "type": "list", "title": "root_typegraphs_fn_output", - "policies": [], "items": 4 }, { "type": "object", "title": "Typegraph", - "policies": [], "properties": { "name": 5, "url": 6 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "url": [] + } }, { "type": "string", - "title": "Typegraph_name_string", - "policies": [] + "title": "Typegraph_name_string" }, { "type": "string", "title": "Typegraph_url_string_uri", - "policies": [], "format": "uri" }, { "type": "function", "title": "root_typegraph_fn", - "policies": [ - 0 - ], "input": 8, "output": 9, - "injections": {}, "runtimeConfig": null, "materializer": 2, "rate_weight": null, @@ -101,39 +129,42 @@ { "type": "object", "title": "root_typegraph_fn_input", - "policies": [], "properties": { "name": 5 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [] + } }, { "type": "optional", "title": "root_typegraph_fn_output", - "policies": [], "item": 10, "default_value": null }, { "type": "object", "title": "root_typegraph_fn_output", - "policies": [], "properties": { "name": 5, "url": 6, "serialized": 11 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "url": [], + "serialized": [] + } }, { "type": "function", "title": "root_typegraph_fn_output_serialized_fn", - "policies": [], "input": 2, "output": 5, - "injections": {}, "runtimeConfig": null, "materializer": 3, "rate_weight": null, @@ -142,12 +173,8 @@ { "type": "function", "title": "root_addTypegraph_fn", - "policies": [ - 0 - ], "input": 13, "output": 15, - "injections": {}, "runtimeConfig": null, "materializer": 4, "rate_weight": null, @@ -156,25 +183,27 @@ { "type": "object", "title": "root_addTypegraph_fn_input", - "policies": [], "properties": { "fromString": 14, "secrets": 14, "targetVersion": 5 }, "id": [], - "required": [] + "required": [], + "policies": { + "fromString": [], + "secrets": [], + "targetVersion": [] + } }, { "type": "string", "title": "root_addTypegraph_fn_input_fromString_string_json", - "policies": [], "format": "json" }, { "type": "object", "title": "root_addTypegraph_fn_output", - "policies": [], "properties": { "name": 5, "messages": 16, @@ -182,29 +211,36 @@ "failure": 21 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "messages": [], + "migrations": [], + "failure": [] + } }, { "type": "list", "title": "root_addTypegraph_fn_output_messages_root_addTypegraph_fn_output_messages_struct_list", - "policies": [], "items": 17 }, { "type": "object", "title": "root_addTypegraph_fn_output_messages_struct", - "policies": [], "properties": { "type": 18, "text": 5 }, "id": [], - "required": [] + "required": [], + "policies": { + "type": [], + "text": [] + } }, { "type": "string", "title": "root_addTypegraph_fn_output_messages_struct_type_string_enum", - "policies": [], "enum": [ "\"info\"", "\"warning\"", @@ -214,36 +250,33 @@ { "type": "list", "title": "root_addTypegraph_fn_output_migrations_root_addTypegraph_fn_output_migrations_struct_list", - "policies": [], "items": 20 }, { "type": "object", "title": "root_addTypegraph_fn_output_migrations_struct", - "policies": [], "properties": { "runtime": 5, "migrations": 5 }, "id": [], - "required": [] + "required": [], + "policies": { + "runtime": [], + "migrations": [] + } }, { "type": "optional", "title": "root_addTypegraph_fn_output_failure_root_addTypegraph_fn_input_fromString_string_json_optional", - "policies": [], "item": 14, "default_value": null }, { "type": "function", "title": "root_removeTypegraphs_fn", - "policies": [ - 0 - ], "input": 23, "output": 25, - "injections": {}, "runtimeConfig": null, "materializer": 5, "rate_weight": null, @@ -252,33 +285,29 @@ { "type": "object", "title": "root_removeTypegraphs_fn_input", - "policies": [], "properties": { "names": 24 }, "id": [], - "required": [] + "required": [], + "policies": { + "names": [] + } }, { "type": "list", "title": "root_removeTypegraphs_fn_input_names_Typegraph_name_string_list", - "policies": [], "items": 5 }, { "type": "boolean", - "title": "root_removeTypegraphs_fn_output", - "policies": [] + "title": "root_removeTypegraphs_fn_output" }, { "type": "function", "title": "root_argInfoByPath_fn", - "policies": [ - 0 - ], "input": 27, - "output": 30, - "injections": {}, + "output": 29, "runtimeConfig": null, "materializer": 6, "rate_weight": null, @@ -287,7 +316,6 @@ { "type": "object", "title": "root_argInfoByPath_fn_input", - "policies": [], "properties": { "typegraph": 5, "queryType": 5, @@ -295,109 +323,116 @@ "argPaths": 28 }, "id": [], - "required": [] - }, - { - "type": "list", - "title": "root_argInfoByPath_fn_input_argPaths_root_argInfoByPath_fn_input_argPaths_Typegraph_name_string_list_list", - "policies": [], - "items": 29 + "required": [], + "policies": { + "typegraph": [], + "queryType": [], + "fn": [], + "argPaths": [] + } }, { "type": "list", - "title": "root_argInfoByPath_fn_input_argPaths_Typegraph_name_string_list", - "policies": [], - "items": 5 + "title": "root_argInfoByPath_fn_input_argPaths_root_removeTypegraphs_fn_input_names_Typegraph_name_string_list_list", + "items": 24 }, { "type": "list", "title": "root_argInfoByPath_fn_output", - "policies": [], - "items": 31 + "items": 30 }, { "type": "object", "title": "TypeInfo", - "policies": [], "properties": { "optional": 25, "title": 5, "type": 5, - "enum": 32, - "default": 34, - "format": 35, - "policies": 36, - "fields": 37 + "enum": 31, + "default": 21, + "format": 33, + "policies": 34, + "fields": 36 }, "id": [], - "required": [] + "required": [], + "policies": { + "optional": [], + "title": [], + "type": [], + "enum": [], + "default": [], + "format": [], + "policies": [], + "fields": [] + } }, { "type": "optional", "title": "TypeInfo_enum_TypeInfo_enum_root_addTypegraph_fn_input_fromString_string_json_list_optional", - "policies": [], - "item": 33, + "item": 32, "default_value": null }, { "type": "list", "title": "TypeInfo_enum_root_addTypegraph_fn_input_fromString_string_json_list", - "policies": [], "items": 14 }, - { - "type": "optional", - "title": "TypeInfo_default_root_addTypegraph_fn_input_fromString_string_json_optional", - "policies": [], - "item": 14, - "default_value": null - }, { "type": "optional", "title": "TypeInfo_format_Typegraph_name_string_optional", - "policies": [], "item": 5, "default_value": null }, { "type": "list", - "title": "TypeInfo_policies_Typegraph_name_string_list", - "policies": [], - "items": 5 + "title": "TypeInfo_policies_TypeInfo_policies_struct_list", + "items": 35 + }, + { + "type": "object", + "title": "TypeInfo_policies_struct", + "properties": { + "fieldName": 5, + "policies": 14 + }, + "id": [], + "required": [], + "policies": { + "fieldName": [], + "policies": [] + } }, { "type": "optional", "title": "TypeInfo_fields_TypeInfo_fields_TypeInfo_fields_struct_list_optional", - "policies": [], - "item": 38, + "item": 37, "default_value": null }, { "type": "list", "title": "TypeInfo_fields_TypeInfo_fields_struct_list", - "policies": [], - "items": 39 + "items": 38 }, { "type": "object", "title": "TypeInfo_fields_struct", - "policies": [], "properties": { - "subPath": 29, - "termNode": 31 + "subPath": 24, + "termNode": 30 }, "id": [], - "required": [] + "required": [], + "policies": { + "subPath": [], + "termNode": [] + } }, { "type": "function", "title": "root_findAvailableOperations_fn", - "policies": [ - 0 - ], - "input": 41, - "output": 42, - "injections": {}, + "input": 40, + "output": 41, "runtimeConfig": null, "materializer": 7, "rate_weight": null, @@ -406,37 +441,43 @@ { "type": "object", "title": "root_findAvailableOperations_fn_input", - "policies": [], "properties": { "typegraph": 5 }, "id": [], - "required": [] + "required": [], + "policies": { + "typegraph": [] + } }, { "type": "list", "title": "root_findAvailableOperations_fn_output", - "policies": [], - "items": 43 + "items": 42 }, { "type": "object", "title": "OperationInfo", - "policies": [], "properties": { "name": 5, - "type": 44, - "inputs": 45, - "output": 31, - "outputItem": 47 + "type": 43, + "inputs": 44, + "output": 30, + "outputItem": 46 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "type": [], + "inputs": [], + "output": [], + "outputItem": [] + } }, { "type": "string", "title": "OperationInfo_type_string_enum", - "policies": [], "enum": [ "\"query\"", "\"mutation\"" @@ -445,36 +486,33 @@ { "type": "list", "title": "OperationInfo_inputs_OperationInfo_inputs_struct_list", - "policies": [], - "items": 46 + "items": 45 }, { "type": "object", "title": "OperationInfo_inputs_struct", - "policies": [], "properties": { "name": 5, - "type": 31 + "type": 30 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "type": [] + } }, { "type": "optional", "title": "OperationInfo_outputItem_TypeInfo_optional", - "policies": [], - "item": 31, + "item": 30, "default_value": null }, { "type": "function", "title": "root_findPrismaModels_fn", - "policies": [ - 0 - ], - "input": 41, - "output": 49, - "injections": {}, + "input": 40, + "output": 48, "runtimeConfig": null, "materializer": 8, "rate_weight": null, @@ -483,64 +521,74 @@ { "type": "list", "title": "root_findPrismaModels_fn_output", - "policies": [], - "items": 50 + "items": 49 }, { "type": "object", "title": "PrismaModelInfo", - "policies": [], "properties": { "name": 5, "runtime": 5, - "fields": 51 + "fields": 50 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "runtime": [], + "fields": [] + } }, { "type": "list", "title": "PrismaModelInfo_fields_PrismaModelInfo_fields_struct_list", - "policies": [], - "items": 52 + "items": 51 }, { "type": "object", "title": "PrismaModelInfo_fields_struct", - "policies": [], "properties": { "name": 5, "as_id": 25, - "type": 53 + "type": 52 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "as_id": [], + "type": [] + } }, { "type": "object", "title": "ShallowTypeInfo", - "policies": [], "properties": { "optional": 25, "title": 5, "type": 5, - "enum": 32, - "default": 34, - "format": 35, - "policies": 36 + "enum": 31, + "default": 21, + "format": 33, + "policies": 34 }, "id": [], - "required": [] + "required": [], + "policies": { + "optional": [], + "title": [], + "type": [], + "enum": [], + "default": [], + "format": [], + "policies": [] + } }, { "type": "function", "title": "root_execRawPrismaRead_fn", - "policies": [ - 0 - ], - "input": 55, + "input": 54, "output": 14, - "injections": {}, "runtimeConfig": null, "materializer": 9, "rate_weight": null, @@ -549,47 +597,46 @@ { "type": "object", "title": "root_execRawPrismaRead_fn_input", - "policies": [], "properties": { "typegraph": 5, "runtime": 5, - "query": 56 + "query": 55 }, "id": [], - "required": [] + "required": [], + "policies": { + "typegraph": [], + "runtime": [], + "query": [] + } }, { "type": "either", "title": "PrismaQuery", - "policies": [], "oneOf": [ - 57, - 60 + 56, + 58 ] }, { "type": "object", "title": "PrismaSingleQuery", - "policies": [], "properties": { - "modelName": 58, - "action": 59, + "modelName": 33, + "action": 57, "query": 14 }, "id": [], - "required": [] - }, - { - "type": "optional", - "title": "PrismaSingleQuery_modelName_Typegraph_name_string_optional", - "policies": [], - "item": 5, - "default_value": null + "required": [], + "policies": { + "modelName": [], + "action": [], + "query": [] + } }, { "type": "string", "title": "PrismaQueryTag", - "policies": [], "enum": [ "\"findUnique\"", "\"findFirst\"", @@ -614,48 +661,49 @@ { "type": "object", "title": "PrismaBatchQuery", - "policies": [], "properties": { - "batch": 61, - "transaction": 62 + "batch": 59, + "transaction": 60 }, "id": [], - "required": [] + "required": [], + "policies": { + "batch": [], + "transaction": [] + } }, { "type": "list", "title": "PrismaBatchQuery_batch_PrismaSingleQuery_list", - "policies": [], - "items": 57 + "items": 56 }, { "type": "optional", "title": "PrismaBatchQuery_transaction_PrismaBatchQuery_transaction_struct_optional", - "policies": [], - "item": 63, + "item": 61, "default_value": null }, { "type": "object", "title": "PrismaBatchQuery_transaction_struct", - "policies": [], "properties": { - "isolationLevel": 64 + "isolationLevel": 62 }, "id": [], - "required": [] + "required": [], + "policies": { + "isolationLevel": [] + } }, { "type": "optional", "title": "PrismaBatchQuery_transaction_struct_isolationLevel_PrismaBatchQuery_transaction_struct_isolationLevel_string_enum_optional", - "policies": [], - "item": 65, + "item": 63, "default_value": null }, { "type": "string", "title": "PrismaBatchQuery_transaction_struct_isolationLevel_string_enum", - "policies": [], "enum": [ "\"read uncommitted\"", "\"readuncommitted\"", @@ -670,12 +718,8 @@ { "type": "function", "title": "root_execRawPrismaCreate_fn", - "policies": [ - 0 - ], - "input": 55, + "input": 54, "output": 14, - "injections": {}, "runtimeConfig": null, "materializer": 10, "rate_weight": null, @@ -684,12 +728,8 @@ { "type": "function", "title": "root_execRawPrismaUpdate_fn", - "policies": [ - 0 - ], - "input": 55, + "input": 54, "output": 14, - "injections": {}, "runtimeConfig": null, "materializer": 11, "rate_weight": null, @@ -698,12 +738,8 @@ { "type": "function", "title": "root_execRawPrismaDelete_fn", - "policies": [ - 0 - ], - "input": 55, + "input": 54, "output": 14, - "injections": {}, "runtimeConfig": null, "materializer": 12, "rate_weight": null, @@ -712,12 +748,8 @@ { "type": "function", "title": "root_queryPrismaModel_fn", - "policies": [ - 0 - ], - "input": 70, - "output": 72, - "injections": {}, + "input": 68, + "output": 70, "runtimeConfig": null, "materializer": 13, "rate_weight": null, @@ -726,45 +758,42 @@ { "type": "object", "title": "root_queryPrismaModel_fn_input", - "policies": [], "properties": { "typegraph": 5, "runtime": 5, "model": 5, - "offset": 71, - "limit": 71 + "offset": 69, + "limit": 69 }, "id": [], - "required": [] + "required": [], + "policies": { + "typegraph": [], + "runtime": [], + "model": [], + "offset": [], + "limit": [] + } }, { "type": "integer", - "title": "root_queryPrismaModel_fn_input_offset_integer", - "policies": [] + "title": "root_queryPrismaModel_fn_input_offset_integer" }, { "type": "object", "title": "root_queryPrismaModel_fn_output", - "policies": [], "properties": { - "fields": 73, - "rowCount": 71, - "data": 74 + "fields": 50, + "rowCount": 69, + "data": 32 }, "id": [], - "required": [] - }, - { - "type": "list", - "title": "root_queryPrismaModel_fn_output_fields_PrismaModelInfo_fields_struct_list", - "policies": [], - "items": 52 - }, - { - "type": "list", - "title": "root_queryPrismaModel_fn_output_data_root_addTypegraph_fn_input_fromString_string_json_list", - "policies": [], - "items": 14 + "required": [], + "policies": { + "fields": [], + "rowCount": [], + "data": [] + } } ], "materializers": [ @@ -785,7 +814,7 @@ "idempotent": true }, "data": { - "script": "var _my_lambda = (_args, { context }) => context.username === 'admin'", + "script": "var _my_lambda = (_args, { context }) => context.username === 'admin' ? 'ALLOW' : 'DENY' ", "secrets": [] } }, @@ -956,4 +985,4 @@ "randomSeed": null, "artifacts": {} } -} +} \ No newline at end of file diff --git a/src/typegate/src/typegraphs/typegate.py b/src/typegate/src/typegraphs/typegate.py index 2b65527830..5008c472e9 100644 --- a/src/typegate/src/typegraphs/typegate.py +++ b/src/typegate/src/typegraphs/typegate.py @@ -97,7 +97,8 @@ def typegate(g: Graph): deno = DenoRuntime() admin_only = deno.policy( - "admin_only", code="(_args, { context }) => context.username === 'admin'" + "admin_only", + code="(_args, { context }) => context.username === 'admin' ? 'ALLOW' : 'DENY' ", ) g.auth(Auth.basic(["admin"])) @@ -177,7 +178,9 @@ def typegate(g: Graph): "enum": t.list(t.json()).optional(), "default": t.json().optional(), "format": t.string().optional(), - "policies": t.list(t.string()), + "policies": t.list( + t.struct({"fieldName": t.string(), "policies": t.json()}) + ), }, name="ShallowTypeInfo", ) diff --git a/src/typegraph/core/src/conversion/types.rs b/src/typegraph/core/src/conversion/types.rs index c90f3728bb..e7bed25d7a 100644 --- a/src/typegraph/core/src/conversion/types.rs +++ b/src/typegraph/core/src/conversion/types.rs @@ -3,8 +3,8 @@ use crate::errors::Result; use crate::typegraph::TypegraphContext; -use crate::types::{ExtendedTypeDef, PolicySpec, TypeId}; -use common::typegraph::{PolicyIndices, TypeNode, TypeNodeBase}; +use crate::types::{ExtendedTypeDef, TypeId}; +use common::typegraph::{TypeNode, TypeNodeBase}; use enum_dispatch::enum_dispatch; use std::rc::Rc; @@ -20,25 +20,20 @@ impl TypeConversion for Rc { } } -pub struct BaseBuilderInit<'a, 'b> { - pub ctx: &'a mut TypegraphContext, +pub struct BaseBuilderInit { pub base_name: &'static str, pub type_id: TypeId, pub name: Option, - pub policies: &'b [PolicySpec], } pub struct BaseBuilder { name: String, - policies: Vec, // optional features enumeration: Option>, } -impl<'a, 'b> BaseBuilderInit<'a, 'b> { +impl BaseBuilderInit { pub fn init_builder(self) -> Result { - let policies = self.ctx.register_policy_chain(self.policies)?; - let name = match self.name { Some(name) => name, None => format!("{}_{}_placeholder", self.base_name, self.type_id.0), @@ -46,7 +41,6 @@ impl<'a, 'b> BaseBuilderInit<'a, 'b> { Ok(BaseBuilder { name, - policies, enumeration: None, }) } @@ -57,7 +51,6 @@ impl BaseBuilder { Ok(TypeNodeBase { description: None, enumeration: self.enumeration, - policies: self.policies, title: self.name, }) } diff --git a/src/typegraph/core/src/global_store.rs b/src/typegraph/core/src/global_store.rs index e318e6d091..7007704203 100644 --- a/src/typegraph/core/src/global_store.rs +++ b/src/typegraph/core/src/global_store.rs @@ -89,7 +89,7 @@ impl Store { effect: Effect::Read, data: MaterializerData::Deno(Rc::new(DenoMaterializer::Predefined( crate::wit::runtimes::MaterializerDenoPredefined { - name: "true".to_string(), + name: "pass".to_string(), }, ))), }], @@ -485,6 +485,10 @@ impl TypeId { Ok(matches!(self.as_xdef()?.type_def, TypeDef::Func(_))) } + pub fn is_struct(&self) -> Result { + Ok(matches!(self.as_xdef()?.type_def, TypeDef::Struct(_))) + } + pub fn resolve_quant(&self) -> Result { let type_id = *self; match type_id.as_xdef()?.type_def { diff --git a/src/typegraph/core/src/lib.rs b/src/typegraph/core/src/lib.rs index 37f9126574..8c33565b5c 100644 --- a/src/typegraph/core/src/lib.rs +++ b/src/typegraph/core/src/lib.rs @@ -214,13 +214,16 @@ impl wit::core::Guest for Lib { .to_string(); let check = match check { - ContextCheck::NotNull => "value != null".to_string(), + ContextCheck::NotNull => "value != null ? 'PASS' : 'DENY'".to_string(), ContextCheck::Value(val) => { - format!("value === {}", serde_json::to_string(&val).unwrap()) + format!( + "value === {} ? 'PASS' : 'DENY'", + serde_json::to_string(&val).unwrap() + ) } ContextCheck::Pattern(pattern) => { format!( - "new RegExp({}).test(value)", + "new RegExp({}).test(value) ? 'PASS' : 'DENY' ", serde_json::to_string(&pattern).unwrap() ) } diff --git a/src/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap b/src/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap index 048b0a6aee..29f6ea430b 100644 --- a/src/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap +++ b/src/typegraph/core/src/snapshots/typegraph_core__tests__successful_serialization.snap @@ -7,7 +7,6 @@ expression: typegraph.0 { "type": "object", "title": "test", - "policies": [], "properties": { "one": 1 }, @@ -19,7 +18,6 @@ expression: typegraph.0 { "type": "function", "title": "root_one_fn", - "policies": [], "input": 2, "output": 4, "runtimeConfig": null, @@ -30,44 +28,43 @@ expression: typegraph.0 { "type": "object", "title": "root_one_fn_input", - "policies": [], "properties": { "one": 3, "two": 4, "three": 5 }, "id": [], - "required": [] + "required": [], + "policies": { + "one": [], + "two": [], + "three": [] + } }, { "type": "integer", - "title": "root_one_fn_input_one_integer", - "policies": [] + "title": "root_one_fn_input_one_integer" }, { "type": "integer", "title": "root_one_fn_input_two_integer", - "policies": [], "minimum": 12, "maximum": 44 }, { "type": "optional", "title": "root_one_fn_input_three_root_one_fn_input_three_root_one_fn_input_three_float_list_optional", - "policies": [], "item": 6, "default_value": null }, { "type": "list", "title": "root_one_fn_input_three_root_one_fn_input_three_float_list", - "policies": [], "items": 7 }, { "type": "float", - "title": "root_one_fn_input_three_float", - "policies": [] + "title": "root_one_fn_input_three_float" } ], "materializers": [ diff --git a/src/typegraph/core/src/typedef/boolean.rs b/src/typegraph/core/src/typedef/boolean.rs index 652e761b7f..786e9e097c 100644 --- a/src/typegraph/core/src/typedef/boolean.rs +++ b/src/typegraph/core/src/typedef/boolean.rs @@ -11,19 +11,17 @@ use crate::{ }, errors, typegraph::TypegraphContext, - types::{Boolean, ExtendedTypeDef, FindAttribute as _, TypeBoolean, TypeDefData}, + types::{Boolean, ExtendedTypeDef, TypeBoolean, TypeDefData}, }; use std::hash::Hash; impl TypeConversion for Boolean { - fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { + fn convert(&self, _ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::Boolean { base: BaseBuilderInit { - ctx, base_name: "boolean", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .build()?, diff --git a/src/typegraph/core/src/typedef/either.rs b/src/typegraph/core/src/typedef/either.rs index afae92ca06..a90b9c2a45 100644 --- a/src/typegraph/core/src/typedef/either.rs +++ b/src/typegraph/core/src/typedef/either.rs @@ -13,7 +13,7 @@ use crate::{ }, errors, typegraph::TypegraphContext, - types::{Either, ExtendedTypeDef, FindAttribute as _, TypeDefData, TypeId}, + types::{Either, ExtendedTypeDef, TypeDefData, TypeId}, wit::core::TypeEither, }; @@ -21,11 +21,9 @@ impl TypeConversion for Either { fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::Either { base: BaseBuilderInit { - ctx, base_name: "either", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .build()?, diff --git a/src/typegraph/core/src/typedef/file.rs b/src/typegraph/core/src/typedef/file.rs index f1550a63af..0cb4660831 100644 --- a/src/typegraph/core/src/typedef/file.rs +++ b/src/typegraph/core/src/typedef/file.rs @@ -9,7 +9,7 @@ use crate::conversion::hash::Hashable; use crate::conversion::types::{BaseBuilderInit, TypeConversion}; use crate::errors::Result; use crate::typegraph::TypegraphContext; -use crate::types::{ExtendedTypeDef, File, FindAttribute as _, TypeDefData}; +use crate::types::{ExtendedTypeDef, File, TypeDefData}; use crate::wit::core::TypeFile; impl TypeDefData for TypeFile { @@ -49,15 +49,13 @@ impl Hashable for TypeFile { } impl TypeConversion for File { - fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { + fn convert(&self, _ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::File { // TODO should `as_id` be supported? base: BaseBuilderInit { - ctx, base_name: "file", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .build()?, diff --git a/src/typegraph/core/src/typedef/float.rs b/src/typegraph/core/src/typedef/float.rs index d7e5f24891..6b08a41e9b 100644 --- a/src/typegraph/core/src/typedef/float.rs +++ b/src/typegraph/core/src/typedef/float.rs @@ -14,19 +14,17 @@ use crate::{ }, errors, typegraph::TypegraphContext, - types::{ExtendedTypeDef, FindAttribute as _, Float, TypeDefData}, + types::{ExtendedTypeDef, Float, TypeDefData}, wit::core::TypeFloat, }; impl TypeConversion for Float { - fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { + fn convert(&self, _ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::Float { base: BaseBuilderInit { - ctx, base_name: "float", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .enum_(self.data.enumeration.as_deref()) diff --git a/src/typegraph/core/src/typedef/func.rs b/src/typegraph/core/src/typedef/func.rs index 377d27e8c2..c6a23630d1 100644 --- a/src/typegraph/core/src/typedef/func.rs +++ b/src/typegraph/core/src/typedef/func.rs @@ -75,11 +75,9 @@ impl TypeConversion for Func { Ok(TypeNode::Function { base: BaseBuilderInit { - ctx, base_name: "func", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .build()?, diff --git a/src/typegraph/core/src/typedef/integer.rs b/src/typegraph/core/src/typedef/integer.rs index 5108b8573b..37e1b2666a 100644 --- a/src/typegraph/core/src/typedef/integer.rs +++ b/src/typegraph/core/src/typedef/integer.rs @@ -13,19 +13,17 @@ use crate::{ }, errors, typegraph::TypegraphContext, - types::{ExtendedTypeDef, FindAttribute as _, Integer, TypeDefData}, + types::{ExtendedTypeDef, Integer, TypeDefData}, wit::core::TypeInteger, }; impl TypeConversion for Integer { - fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { + fn convert(&self, _ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::Integer { base: BaseBuilderInit { - ctx, base_name: "integer", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .enum_(self.data.enumeration.as_deref()) diff --git a/src/typegraph/core/src/typedef/list.rs b/src/typegraph/core/src/typedef/list.rs index 069cb79faf..3ea9e405a2 100644 --- a/src/typegraph/core/src/typedef/list.rs +++ b/src/typegraph/core/src/typedef/list.rs @@ -12,7 +12,7 @@ use crate::{ }, errors::Result, typegraph::TypegraphContext, - types::{ExtendedTypeDef, FindAttribute as _, List, TypeDefData, TypeId}, + types::{ExtendedTypeDef, List, TypeDefData, TypeId}, wit::core::TypeList, }; @@ -20,11 +20,9 @@ impl TypeConversion for List { fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::List { base: BaseBuilderInit { - ctx, base_name: "list", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .build()?, diff --git a/src/typegraph/core/src/typedef/optional.rs b/src/typegraph/core/src/typedef/optional.rs index 77f650838a..cb89e3b68e 100644 --- a/src/typegraph/core/src/typedef/optional.rs +++ b/src/typegraph/core/src/typedef/optional.rs @@ -13,7 +13,7 @@ use crate::{ }, errors, typegraph::TypegraphContext, - types::{ExtendedTypeDef, FindAttribute as _, Optional, TypeDefData, TypeId}, + types::{ExtendedTypeDef, Optional, TypeDefData, TypeId}, wit::core::TypeOptional, }; @@ -29,11 +29,9 @@ impl TypeConversion for Optional { Ok(TypeNode::Optional { base: BaseBuilderInit { - ctx, base_name: "optional", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .build()?, diff --git a/src/typegraph/core/src/typedef/string.rs b/src/typegraph/core/src/typedef/string.rs index 49afb6f13f..57d3e0c9d3 100644 --- a/src/typegraph/core/src/typedef/string.rs +++ b/src/typegraph/core/src/typedef/string.rs @@ -13,12 +13,12 @@ use crate::{ }, errors, typegraph::TypegraphContext, - types::{ExtendedTypeDef, FindAttribute as _, StringT, TypeDefData}, + types::{ExtendedTypeDef, StringT, TypeDefData}, wit::core::TypeString, }; impl TypeConversion for StringT { - fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { + fn convert(&self, _ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { let format: Option = match self.data.format.clone() { Some(format) => { let ret = @@ -30,11 +30,9 @@ impl TypeConversion for StringT { Ok(TypeNode::String { base: BaseBuilderInit { - ctx, base_name: "string", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .enum_(self.data.enumeration.as_deref()) diff --git a/src/typegraph/core/src/typedef/struct_.rs b/src/typegraph/core/src/typedef/struct_.rs index c1481bcba5..3f7338e1f4 100644 --- a/src/typegraph/core/src/typedef/struct_.rs +++ b/src/typegraph/core/src/typedef/struct_.rs @@ -4,10 +4,11 @@ use crate::conversion::hash::Hashable; use crate::conversion::types::{BaseBuilderInit, TypeConversion}; use crate::types::{ - AsTypeDefEx as _, ExtendedTypeDef, FindAttribute as _, IdKind, Struct, TypeDefData, TypeId, + AsTypeDefEx as _, ExtendedTypeDef, FindAttribute as _, IdKind, PolicySpec, Struct, TypeDef, + TypeDefData, TypeId, }; use crate::{errors, typegraph::TypegraphContext, wit::core::TypeStruct}; -use common::typegraph::{ObjectTypeData, TypeNode}; +use common::typegraph::{ObjectTypeData, PolicyIndices, TypeNode}; use errors::Result; use indexmap::IndexMap; use std::hash::Hash as _; @@ -63,11 +64,9 @@ impl TypeConversion for Struct { fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::Object { base: BaseBuilderInit { - ctx, base_name: "object", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .enum_(self.data.enumeration.as_deref()) @@ -81,6 +80,7 @@ impl TypeConversion for Struct { .collect::>>()?, id: self.data.find_id_fields()?, required: Vec::new(), + policies: self.data.collect_policies(ctx)?, }, }) } @@ -126,3 +126,65 @@ impl TypeStruct { .find_map(|(n, t)| if n == name { Some(t.into()) } else { None }) } } + +impl TypeStruct { + fn collect_policies( + &self, + ctx: &mut TypegraphContext, + ) -> Result>> { + let mut res = IndexMap::new(); + for (name, tpe_id) in &self.props { + let mut chain = vec![]; + extend_policy_chain(&mut chain, TypeId(*tpe_id))?; + + res.insert(name.clone(), ctx.register_policy_chain(&chain)?); + } + Ok(res) + } +} + +pub fn extend_policy_chain(chain: &mut Vec, type_id: TypeId) -> Result<()> { + let xdef = type_id.as_xdef()?; + let policies = xdef.attributes.find_policy().unwrap_or(&[]); + chain.extend(policies.iter().cloned()); + + match xdef.type_def { + TypeDef::Optional(inner) => { + extend_policy_chain(chain, TypeId(inner.data.of))?; + } + TypeDef::List(inner) => { + extend_policy_chain(chain, TypeId(inner.data.of))?; + } + TypeDef::Union(inner) => { + for tpe_id in inner.data.variants.iter() { + let tpe = TypeId(*tpe_id); + if !tpe.is_struct()? { + extend_policy_chain(chain, tpe)?; + } + } + } + TypeDef::Either(inner) => { + for tpe_id in inner.data.variants.iter() { + let tpe = TypeId(*tpe_id); + if !tpe.is_struct()? { + extend_policy_chain(chain, tpe)?; + } + } + } + TypeDef::Func(inner) => { + extend_policy_chain(chain, TypeId(inner.data.out))?; + } + TypeDef::Struct(_) => { + // noop + // struct is the boundary + } + // scalar types + TypeDef::Integer(_) + | TypeDef::Float(_) + | TypeDef::Boolean(_) + | TypeDef::String(_) + | TypeDef::File(_) => {} + } + + Ok(()) +} diff --git a/src/typegraph/core/src/typedef/union.rs b/src/typegraph/core/src/typedef/union.rs index c037202fcd..1eeb0b9240 100644 --- a/src/typegraph/core/src/typedef/union.rs +++ b/src/typegraph/core/src/typedef/union.rs @@ -13,7 +13,7 @@ use crate::{ }, errors, typegraph::TypegraphContext, - types::{ExtendedTypeDef, FindAttribute as _, TypeDefData, TypeId, Union}, + types::{ExtendedTypeDef, TypeDefData, TypeId, Union}, wit::core::TypeUnion, }; @@ -21,11 +21,9 @@ impl TypeConversion for Union { fn convert(&self, ctx: &mut TypegraphContext, xdef: ExtendedTypeDef) -> Result { Ok(TypeNode::Union { base: BaseBuilderInit { - ctx, base_name: "union", type_id: self.id, name: xdef.get_owned_name(), - policies: xdef.attributes.find_policy().unwrap_or(&[]), } .init_builder()? .build()?, diff --git a/src/typegraph/core/src/typegraph.rs b/src/typegraph/core/src/typegraph.rs index fe28b50702..e6ee32fda7 100644 --- a/src/typegraph/core/src/typegraph.rs +++ b/src/typegraph/core/src/typegraph.rs @@ -5,6 +5,7 @@ use crate::conversion::hash::Hasher; use crate::conversion::runtimes::{convert_materializer, convert_runtime, ConvertedRuntime}; use crate::conversion::types::TypeConversion as _; use crate::global_store::SavedState; +use crate::typedef::struct_::extend_policy_chain; use crate::types::{ AsTypeDefEx as _, FindAttribute as _, PolicySpec, TypeDef, TypeDefExt, TypeId, WithPolicy, }; @@ -129,11 +130,11 @@ pub fn init(params: TypegraphInitParams) -> Result<()> { base: TypeNodeBase { description: None, enumeration: None, - policies: Default::default(), title: params.name, }, data: ObjectTypeData { properties: IndexMap::new(), + policies: Default::default(), id: vec![], required: vec![], }, @@ -320,9 +321,16 @@ pub fn expose( return Err(errors::duplicate_export_name(&key)); } ensure_valid_export(key.clone(), type_id)?; + let mut policy_chain = vec![]; + extend_policy_chain(&mut policy_chain, type_id)?; let type_idx = ctx.register_type(type_id)?; root_data.properties.insert(key.clone(), type_idx.into()); + if !policy_chain.is_empty() { + root_data + .policies + .insert(key.clone(), ctx.register_policy_chain(&policy_chain)?); + } root_data.required.push(key); Ok(()) }) diff --git a/src/typegraph/core/src/validation/materializers.rs b/src/typegraph/core/src/validation/materializers.rs index 23c3023b4f..c05459aebe 100644 --- a/src/typegraph/core/src/validation/materializers.rs +++ b/src/typegraph/core/src/validation/materializers.rs @@ -48,6 +48,18 @@ impl Materializer { }; } } + + "allow" | "deny" | "pass" => { + if let Ok(xdef) = TypeId(func.out).as_xdef() { + let TypeDef::String(_) = xdef.type_def else { + return Err(errors::invalid_output_type_predefined( + &predef.name, + "string", + &TypeId(func.out).repr()?, + )); + }; + } + } _ => { return Err(errors::unknown_predefined_function(&predef.name)); } diff --git a/src/typegraph/deno/src/types.ts b/src/typegraph/deno/src/types.ts index 3731d465a9..fda9c2556c 100644 --- a/src/typegraph/deno/src/types.ts +++ b/src/typegraph/deno/src/types.ts @@ -81,7 +81,7 @@ export function getPolicyChain( export class Typedef { readonly name?: string; - policy: Policy[] | null = null; + policy: WitPolicySpec[] | null = null; constructor(public readonly _id: number) {} @@ -90,15 +90,16 @@ export class Typedef { } withPolicy(policy: PolicySpec[] | PolicySpec): this { - const id = core.withPolicy(this._id, getPolicyChain(policy)); + const chain = getPolicyChain(policy); + const newChain = [...(this.policy ?? []), ...chain]; + const id = core.withPolicy(this._id, newChain); - const chain = Array.isArray(policy) ? policy : [policy]; return new Proxy(this, { get(target, prop, receiver) { if (prop === "_id") { return id; } else if (prop === "policy") { - return chain; + return newChain; } else { return Reflect.get(target, prop, receiver); } diff --git a/src/typegraph/python/typegraph/t.py b/src/typegraph/python/typegraph/t.py index 5e6215d0cf..8433ffdf74 100644 --- a/src/typegraph/python/typegraph/t.py +++ b/src/typegraph/python/typegraph/t.py @@ -75,13 +75,14 @@ def __repr__(self): def with_policy(self, *policies: Optional[PolicySpec]) -> Self: policy_chain = get_policy_chain(policies) - res = core.with_policy(store, self._id, policy_chain) + new_policy_chain = (self.policy_chain or []) + policy_chain + res = core.with_policy(store, self._id, new_policy_chain) if isinstance(res, Err): raise ErrorStack(res.value) ret = copy.copy(self) ret._id = res.value - ret.policy_chain = policy_chain + ret.policy_chain = new_policy_chain return ret def rename(self, name: str) -> Self: diff --git a/tests/auth/auth.py b/tests/auth/auth.py index e3a1703de7..d40ea412df 100644 --- a/tests/auth/auth.py +++ b/tests/auth/auth.py @@ -17,9 +17,12 @@ def auth(g: Graph): remote = HttpRuntime("https://api.github.com") public = Policy.public() - private = deno.policy("private", "(_args, { context }) => !!context.user1") + private = deno.policy( + "private", "(_args, { context }) => !!context.user1 ? 'ALLOW' : 'DENY'" + ) with_token = deno.policy( - "with_token", "(_args, { context }) => { return !!context.accessToken; }" + "with_token", + "(_args, { context }) => { return !!context.accessToken ? 'ALLOW' : 'DENY'; }", ) x = t.struct({"x": t.integer()}) diff --git a/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap b/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap index b81a657e97..4f0039f62f 100644 --- a/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap +++ b/tests/e2e/typegraph/__snapshots__/typegraph_test.ts.snap @@ -7,21 +7,22 @@ snapshot[`typegraphs creation 1`] = ` { "type": "object", "title": "test-complex-types", - "policies": [], "properties": { "test": 1 }, "id": [], "required": [ "test" - ] + ], + "policies": { + "test": [ + 0 + ] + } }, { "type": "function", "title": "root_test_fn", - "policies": [ - 0 - ], "input": 2, "output": 18, "runtimeConfig": null, @@ -32,7 +33,6 @@ snapshot[`typegraphs creation 1`] = ` { "type": "object", "title": "ComplexType", - "policies": [], "properties": { "a_string": 3, "a_float": 4, @@ -45,24 +45,32 @@ snapshot[`typegraphs creation 1`] = ` "an_email": 17 }, "id": [], - "required": [] + "required": [], + "policies": { + "a_string": [], + "a_float": [], + "an_enum": [], + "an_integer_enum": [], + "a_float_enum": [], + "a_struct": [], + "nested": [], + "nested_with_ref": [], + "an_email": [] + } }, { "type": "string", - "title": "ComplexType_a_string_string", - "policies": [] + "title": "ComplexType_a_string_string" }, { "type": "float", "title": "ComplexType_a_float_float", - "policies": [], "minimum": 1.0, "multipleOf": 2.0 }, { "type": "string", "title": "ComplexType_an_enum_string_enum", - "policies": [], "enum": [ "\\\\"one\\\\"", "\\\\"two\\\\"" @@ -71,7 +79,6 @@ snapshot[`typegraphs creation 1`] = ` { "type": "integer", "title": "ComplexType_an_integer_enum_integer_enum", - "policies": [], "enum": [ "1", "2" @@ -80,7 +87,6 @@ snapshot[`typegraphs creation 1`] = ` { "type": "float", "title": "ComplexType_a_float_enum_float", - "policies": [], "enum": [ "1.5", "2.5" @@ -89,35 +95,33 @@ snapshot[`typegraphs creation 1`] = ` { "type": "object", "title": "ComplexType_a_struct_struct", - "policies": [], "properties": { "value": 9 }, "id": [], - "required": [] + "required": [], + "policies": { + "value": [] + } }, { "type": "float", - "title": "ComplexType_a_struct_struct_value_float", - "policies": [] + "title": "ComplexType_a_struct_struct_value_float" }, { "type": "optional", "title": "ComplexType_nested_ComplexType_nested_ComplexType_nested_either_list_optional", - "policies": [], "item": 11, "default_value": null }, { "type": "list", "title": "ComplexType_nested_ComplexType_nested_either_list", - "policies": [], "items": 12 }, { "type": "either", "title": "ComplexType_nested_either", - "policies": [], "oneOf": [ 3, 13 @@ -125,44 +129,42 @@ snapshot[`typegraphs creation 1`] = ` }, { "type": "integer", - "title": "ComplexType_nested_either_t1_integer", - "policies": [] + "title": "ComplexType_nested_either_t1_integer" }, { "type": "object", "title": "SomeType", - "policies": [], "properties": { "one": 15, "two": 16 }, "id": [], - "required": [] + "required": [], + "policies": { + "one": [], + "two": [] + } }, { "type": "list", "title": "Two", - "policies": [], "items": 13, "minItems": 3 }, { "type": "optional", "title": "SomeType_two_SomeType_optional", - "policies": [], "item": 14, "default_value": null }, { "type": "string", "title": "ComplexType_an_email_string_email", - "policies": [], "format": "email" }, { "type": "boolean", - "title": "root_test_fn_output", - "policies": [] + "title": "root_test_fn_output" } ], "materializers": [ @@ -186,7 +188,7 @@ snapshot[`typegraphs creation 1`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } } ], @@ -270,7 +272,6 @@ snapshot[`typegraphs creation 2`] = ` { "type": "object", "title": "test-multiple-runtimes", - "policies": [], "properties": { "add": 1, "multiply": 4 @@ -279,14 +280,19 @@ snapshot[`typegraphs creation 2`] = ` "required": [ "add", "multiply" - ] + ], + "policies": { + "add": [ + 0 + ], + "multiply": [ + 0 + ] + } }, { "type": "function", "title": "root_add_fn", - "policies": [ - 0 - ], "input": 2, "output": 3, "runtimeConfig": null, @@ -297,25 +303,24 @@ snapshot[`typegraphs creation 2`] = ` { "type": "object", "title": "root_add_fn_input", - "policies": [], "properties": { "first": 3, "second": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "first": [], + "second": [] + } }, { "type": "float", - "title": "root_add_fn_input_first_float", - "policies": [] + "title": "root_add_fn_input_first_float" }, { "type": "function", "title": "root_multiply_fn", - "policies": [ - 0 - ], "input": 2, "output": 3, "runtimeConfig": null, @@ -345,7 +350,7 @@ snapshot[`typegraphs creation 2`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { @@ -408,13 +413,12 @@ snapshot[`typegraphs creation 2`] = ` `; snapshot[`typegraphs creation 3`] = ` -'[ +\`[ { "types": [ { "type": "object", "title": "test-types", - "policies": [], "properties": { "one": 1, "two": 6, @@ -425,14 +429,22 @@ snapshot[`typegraphs creation 3`] = ` "one", "two", "three" - ] + ], + "policies": { + "one": [ + 0 + ], + "two": [ + 1 + ], + "three": [ + 2 + ] + } }, { "type": "function", "title": "root_one_fn", - "policies": [ - 0 - ], "input": 2, "output": 5, "runtimeConfig": null, @@ -443,38 +455,35 @@ snapshot[`typegraphs creation 3`] = ` { "type": "object", "title": "root_one_fn_input", - "policies": [], "properties": { "a": 3, "b": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "a": [], + "b": [] + } }, { "type": "integer", - "title": "root_one_fn_input_a_integer", - "policies": [] + "title": "root_one_fn_input_a_integer" }, { "type": "integer", "title": "root_one_fn_input_b_integer", - "policies": [], "minimum": 12 }, { "type": "integer", "title": "root_one_fn_output", - "policies": [], "minimum": 12, "maximum": 43 }, { "type": "function", "title": "root_two_fn", - "policies": [ - 1 - ], "input": 7, "output": 8, "runtimeConfig": null, @@ -485,31 +494,34 @@ snapshot[`typegraphs creation 3`] = ` { "type": "object", "title": "User", - "policies": [], "properties": { "id": 3, "post": 8 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "post": [] + } }, { "type": "object", "title": "Post", - "policies": [], "properties": { "id": 3, "author": 7 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "author": [] + } }, { "type": "function", "title": "root_three_fn", - "policies": [ - 2 - ], "input": 2, "output": 2, "runtimeConfig": null, @@ -562,7 +574,7 @@ snapshot[`typegraphs creation 3`] = ` "idempotent": true }, "data": { - "script": "var _my_lambda = () => false", + "script": "var _my_lambda = () => 'DENY'", "secrets": [] } }, @@ -599,7 +611,7 @@ snapshot[`typegraphs creation 3`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } } ], @@ -654,7 +666,7 @@ snapshot[`typegraphs creation 3`] = ` } } } -]' +]\` `; snapshot[`typegraphs creation 4`] = ` @@ -664,21 +676,22 @@ snapshot[`typegraphs creation 4`] = ` { "type": "object", "title": "test-complex-types", - "policies": [], "properties": { "test": 1 }, "id": [], "required": [ "test" - ] + ], + "policies": { + "test": [ + 0 + ] + } }, { "type": "function", "title": "root_test_fn", - "policies": [ - 0 - ], "input": 2, "output": 18, "runtimeConfig": null, @@ -689,7 +702,6 @@ snapshot[`typegraphs creation 4`] = ` { "type": "object", "title": "ComplexType", - "policies": [], "properties": { "a_string": 3, "a_float": 4, @@ -702,24 +714,32 @@ snapshot[`typegraphs creation 4`] = ` "an_email": 17 }, "id": [], - "required": [] + "required": [], + "policies": { + "a_string": [], + "a_float": [], + "an_enum": [], + "an_integer_enum": [], + "a_float_enum": [], + "a_struct": [], + "nested": [], + "nested_with_ref": [], + "an_email": [] + } }, { "type": "string", - "title": "ComplexType_a_string_string", - "policies": [] + "title": "ComplexType_a_string_string" }, { "type": "float", "title": "ComplexType_a_float_float", - "policies": [], "minimum": 1.0, "multipleOf": 2.0 }, { "type": "string", "title": "ComplexType_an_enum_string_enum", - "policies": [], "enum": [ "\\\\"one\\\\"", "\\\\"two\\\\"" @@ -728,7 +748,6 @@ snapshot[`typegraphs creation 4`] = ` { "type": "integer", "title": "ComplexType_an_integer_enum_integer_enum", - "policies": [], "enum": [ "1", "2" @@ -737,7 +756,6 @@ snapshot[`typegraphs creation 4`] = ` { "type": "float", "title": "ComplexType_a_float_enum_float", - "policies": [], "enum": [ "1.5", "2.5" @@ -746,35 +764,33 @@ snapshot[`typegraphs creation 4`] = ` { "type": "object", "title": "ComplexType_a_struct_struct", - "policies": [], "properties": { "value": 9 }, "id": [], - "required": [] + "required": [], + "policies": { + "value": [] + } }, { "type": "float", - "title": "ComplexType_a_struct_struct_value_float", - "policies": [] + "title": "ComplexType_a_struct_struct_value_float" }, { "type": "optional", "title": "ComplexType_nested_ComplexType_nested_ComplexType_nested_either_list_optional", - "policies": [], "item": 11, "default_value": null }, { "type": "list", "title": "ComplexType_nested_ComplexType_nested_either_list", - "policies": [], "items": 12 }, { "type": "either", "title": "ComplexType_nested_either", - "policies": [], "oneOf": [ 3, 13 @@ -782,44 +798,42 @@ snapshot[`typegraphs creation 4`] = ` }, { "type": "integer", - "title": "ComplexType_nested_either_t1_integer", - "policies": [] + "title": "ComplexType_nested_either_t1_integer" }, { "type": "object", "title": "SomeType", - "policies": [], "properties": { "one": 15, "two": 16 }, "id": [], - "required": [] + "required": [], + "policies": { + "one": [], + "two": [] + } }, { "type": "list", "title": "Two", - "policies": [], "items": 13, "minItems": 3 }, { "type": "optional", "title": "SomeType_two_SomeType_optional", - "policies": [], "item": 14, "default_value": null }, { "type": "string", "title": "ComplexType_an_email_string_email", - "policies": [], "format": "email" }, { "type": "boolean", - "title": "root_test_fn_output", - "policies": [] + "title": "root_test_fn_output" } ], "materializers": [ @@ -843,7 +857,7 @@ snapshot[`typegraphs creation 4`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } } ], @@ -927,7 +941,6 @@ snapshot[`typegraphs creation 5`] = ` { "type": "object", "title": "test-multiple-runtimes", - "policies": [], "properties": { "add": 1, "multiply": 4 @@ -936,14 +949,19 @@ snapshot[`typegraphs creation 5`] = ` "required": [ "add", "multiply" - ] + ], + "policies": { + "add": [ + 0 + ], + "multiply": [ + 0 + ] + } }, { "type": "function", "title": "root_add_fn", - "policies": [ - 0 - ], "input": 2, "output": 3, "runtimeConfig": null, @@ -954,25 +972,24 @@ snapshot[`typegraphs creation 5`] = ` { "type": "object", "title": "root_add_fn_input", - "policies": [], "properties": { "first": 3, "second": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "first": [], + "second": [] + } }, { "type": "float", - "title": "root_add_fn_input_first_float", - "policies": [] + "title": "root_add_fn_input_first_float" }, { "type": "function", "title": "root_multiply_fn", - "policies": [ - 0 - ], "input": 2, "output": 3, "runtimeConfig": null, @@ -1002,7 +1019,7 @@ snapshot[`typegraphs creation 5`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { @@ -1065,13 +1082,12 @@ snapshot[`typegraphs creation 5`] = ` `; snapshot[`typegraphs creation 6`] = ` -'[ +\`[ { "types": [ { "type": "object", "title": "test-types", - "policies": [], "properties": { "one": 1, "two": 6, @@ -1082,14 +1098,22 @@ snapshot[`typegraphs creation 6`] = ` "one", "two", "three" - ] + ], + "policies": { + "one": [ + 0 + ], + "two": [ + 1 + ], + "three": [ + 2 + ] + } }, { "type": "function", "title": "root_one_fn", - "policies": [ - 0 - ], "input": 2, "output": 5, "runtimeConfig": null, @@ -1100,38 +1124,35 @@ snapshot[`typegraphs creation 6`] = ` { "type": "object", "title": "root_one_fn_input", - "policies": [], "properties": { "a": 3, "b": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "a": [], + "b": [] + } }, { "type": "integer", - "title": "root_one_fn_input_a_integer", - "policies": [] + "title": "root_one_fn_input_a_integer" }, { "type": "integer", "title": "root_one_fn_input_b_integer", - "policies": [], "minimum": 12 }, { "type": "integer", "title": "root_one_fn_output", - "policies": [], "minimum": 12, "maximum": 43 }, { "type": "function", "title": "root_two_fn", - "policies": [ - 1 - ], "input": 7, "output": 8, "runtimeConfig": null, @@ -1142,31 +1163,34 @@ snapshot[`typegraphs creation 6`] = ` { "type": "object", "title": "User", - "policies": [], "properties": { "id": 3, "post": 8 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "post": [] + } }, { "type": "object", "title": "Post", - "policies": [], "properties": { "id": 3, "author": 7 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "author": [] + } }, { "type": "function", "title": "root_three_fn", - "policies": [ - 2 - ], "input": 2, "output": 2, "runtimeConfig": null, @@ -1219,7 +1243,7 @@ snapshot[`typegraphs creation 6`] = ` "idempotent": true }, "data": { - "script": "var _my_lambda = () => false", + "script": "var _my_lambda = () => 'DENY'", "secrets": [] } }, @@ -1256,7 +1280,7 @@ snapshot[`typegraphs creation 6`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } } ], @@ -1311,5 +1335,5 @@ snapshot[`typegraphs creation 6`] = ` } } } -]' +]\` `; diff --git a/tests/e2e/typegraph/typegraphs/deno/simple.ts b/tests/e2e/typegraph/typegraphs/deno/simple.ts index 7546b95cd1..eff5d48158 100644 --- a/tests/e2e/typegraph/typegraphs/deno/simple.ts +++ b/tests/e2e/typegraph/typegraphs/deno/simple.ts @@ -41,7 +41,7 @@ typegraph("test-types", (g: any) => { .func(user, post, { code: "(user) => ({ id: 12, user })", }) - .withPolicy(deno.policy("deny", "() => false")), + .withPolicy(deno.policy("deny", "() => 'DENY'")), three: deno .import(s1, s1, { name: "three", module: "scripts/three.ts" }) .withPolicy(pub), diff --git a/tests/e2e/typegraph/typegraphs/python/simple.py b/tests/e2e/typegraph/typegraphs/python/simple.py index 0deca0f764..052da9d48f 100644 --- a/tests/e2e/typegraph/typegraphs/python/simple.py +++ b/tests/e2e/typegraph/typegraphs/python/simple.py @@ -23,7 +23,7 @@ def test_types(g: Graph): g.expose( one=deno.func(s1, b, code="() => 12").with_policy(internal), two=deno.func(user, post, code="(user) => ({ id: 12, user })").with_policy( - deno.policy("deny", "() => false") + deno.policy("deny", "() => 'DENY'") ), three=deno.import_(s1, s1, name="three", module="scripts/three.ts").with_policy( public diff --git a/tests/planner/__snapshots__/planner_test.ts.snap b/tests/planner/__snapshots__/planner_test.ts.snap index d62e8a890e..3a67182e53 100644 --- a/tests/planner/__snapshots__/planner_test.ts.snap +++ b/tests/planner/__snapshots__/planner_test.ts.snap @@ -95,81 +95,75 @@ snapshot[`planner 1`] = ` snapshot[`planner 2`] = ` { - one: { - funcType: { - input: 2, - materializer: 0, - output: 3, - policies: [ + "": [ + { + canonFieldName: "one", + indices: [ 0, ], - rate_calls: false, - rate_weight: null, - runtimeConfig: null, - title: "root_one_fn", - type: "function", }, - referencedTypes: { - "one.email": [ - { - title: "root_one_fn_output_email_string_email", - type: "string", - }, - ], - "one.id": [ - { - title: "root_one_fn_output_id_string_uuid", - type: "string", - }, - ], - "one.nested": [ - { - title: "root_one_fn_output_nested_struct", - type: "object", - }, - ], - "one.nested.first": [ - { - title: "root_one_fn_output_nested_struct_first_string", - type: "string", - }, - ], - "one.nested.second": [ - { - title: "root_one_fn_output_nested_struct_second_root_one_fn_output_nested_struct_second_float_list", - type: "list", - }, - { - title: "root_one_fn_output_nested_struct_second_float", - type: "float", - }, - ], - "one.nested.third": [ - { - title: "root_one_fn_output_nested_struct_third_root_one_fn_output_nested_struct_third_boolean_optional", - type: "optional", - }, - { - title: "root_one_fn_output_nested_struct_third_boolean", - type: "boolean", - }, + { + canonFieldName: "two", + indices: [ + 0, ], - one: [ - { - title: "root_one_fn", - type: "function", - }, - { - title: "root_one_fn_output", - type: "object", - }, - { - title: "root_one_fn_input", - type: "object", - }, + }, + { + canonFieldName: "three", + indices: [ + 0, ], }, - }, + ], + "one.email": [], + "one.id": [], + "one.nested": [ + { + canonFieldName: "first", + indices: [], + }, + { + canonFieldName: "second", + indices: [], + }, + { + canonFieldName: "third", + indices: [], + }, + ], + "one.nested.first": [], + "one.nested.second": [], + "one.nested.third": [], + one: [ + { + canonFieldName: "id", + indices: [], + }, + { + canonFieldName: "email", + indices: [], + }, + { + canonFieldName: "nested", + indices: [], + }, + { + canonFieldName: "union1", + indices: [], + }, + { + canonFieldName: "union2", + indices: [], + }, + { + canonFieldName: "from_union1", + indices: [], + }, + { + canonFieldName: "from_union2", + indices: [], + }, + ], } `; diff --git a/tests/planner/planner_test.ts b/tests/planner/planner_test.ts index c0797c6a32..a071ff5a34 100644 --- a/tests/planner/planner_test.ts +++ b/tests/planner/planner_test.ts @@ -36,22 +36,8 @@ Meta.test("planner", async (t) => { }); await t.should("generate the right policy tree", async () => { - const fns = Object.fromEntries(plan.policies.functions); - await t.assertSnapshot( - mapValues(fns, (subtree) => { - const funcType = e.tg.type(subtree.funcTypeIdx); - return { - funcType, - referencedTypes: mapValues( - Object.fromEntries(subtree.referencedTypes), - (types) => - types.map((idx) => - filterKeys(e.tg.type(idx), (k) => ["type", "title"].includes(k)) - ), - ), - }; - }), - ); + const stageToPolicies = Object.fromEntries(plan.policies.stageToPolicies); + await t.assertSnapshot(stageToPolicies); }); await t.should("fail when required selections are missing", async () => { diff --git a/tests/policies/effects_py.py b/tests/policies/effects_py.py index 0bbbd06898..4de867507f 100644 --- a/tests/policies/effects_py.py +++ b/tests/policies/effects_py.py @@ -11,13 +11,15 @@ def effects_py(g: Graph): deno = DenoRuntime() public = Policy.public() admin_only = Policy.context("role", "admin") + deny_all = deno.policy("deny_all", "_ => 'DENY'") + # allow_all = deno.policy("allow_all", "_ => 'ALLOW'") user = t.struct( { "id": t.integer(), "email": t.email(), "password_hash": t.string().with_policy( - deno.policy("deny_all", "() => false") + Policy.on(read=deny_all, update=admin_only) ), }, name="User", diff --git a/tests/policies/policies.py b/tests/policies/policies.py index 78e308364f..273ad8287d 100644 --- a/tests/policies/policies.py +++ b/tests/policies/policies.py @@ -10,14 +10,6 @@ def policies(g: Graph): deno = DenoRuntime() - _secret_data = t.struct( - { - "username": t.string(), - "data": t.string(), - }, - name="SecretData", - ) - fn = deno.identity( t.struct({"a": t.integer()}), ) @@ -25,10 +17,13 @@ def policies(g: Graph): g.auth(Auth.jwt("native", "jwk", {"name": "HMAC", "hash": {"name": "SHA-256"}})) g.expose( - pol_true=fn.with_policy(deno.policy("true", "() => true")), - pol_false=fn.with_policy(deno.policy("false", "() => false")), + pol_pass=fn.with_policy(deno.policy("pass", "() => 'PASS'")), + pol_deny=fn.with_policy(deno.policy("deny", "() => 'DENY'")), pol_two=fn.with_policy( - deno.policy("eq_two", "(_args, { context }) => Number(context.a) === 2") + deno.policy( + "eq_two", + "(_args, { context }) => Number(context.a) === 2 ? 'ALLOW' : 'DENY'", + ) ), ns=t.struct( { diff --git a/tests/policies/policies_composition.py b/tests/policies/policies_composition.py new file mode 100644 index 0000000000..94784abcdd --- /dev/null +++ b/tests/policies/policies_composition.py @@ -0,0 +1,87 @@ +# Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +# SPDX-License-Identifier: MPL-2.0 + +from typegraph import typegraph, effects, t, Graph +from typegraph.policy import Policy +from typegraph.runtimes import DenoRuntime + + +@typegraph() +def policies_composition(g: Graph): + deno = DenoRuntime() + + def ctxread(pol_name: str): + return deno.policy(pol_name, code=f"(_, {{ context }}) => context.{pol_name}") + + deny = deno.policy("denyAll", code="() => 'DENY' ") + allow = deno.policy("allowAll", code="() => 'ALLOW' ") + pass_through = deno.policy("passThrough", code="() => 'PASS' ") # alt public + + big_struct = t.struct( + { + "one": t.struct( + { + "two": t.struct( + { + # Note: + # The policy on each variant is not hoisted + # on the parent struct + "three": t.either( + [ + t.struct({"a": t.integer()}).rename("First"), + t.struct( + { + "b": t.struct( + { + "c": t.integer().with_policy( + Policy.on( + read=allow, + update=ctxread("depth_4"), + ) + ) + } + ) + } + ).rename("Second"), + ] + ).with_policy(ctxread("depth_3")) + } + ).with_policy(Policy.on(read=deny, update=ctxread("depth_2"))) + } + ).with_policy(ctxread("depth_1")) + } + ) + + g.expose( + simple_traversal_comp=deno.identity( + t.struct( + { + "one": t.struct( + { + "two": t.either([t.integer(), t.string()]).with_policy( + deny + ), + "three": t.either([t.integer(), t.string()]).with_policy( + allow + ), + } + ).with_policy(ctxread("control_value")), + } + ) + ).with_policy(pass_through), + single_field_comp=deno.identity( + t.struct( + { + "abc": t.string() + .with_policy(ctxread("A"), ctxread("B")) + .with_policy(ctxread("C")) + } + ) + ).with_policy(pass_through), + traversal_comp=deno.func( + big_struct, + big_struct, + code="({ one }) => ({ one })", + effect=effects.update(), + ).with_policy(pass_through), + ) diff --git a/tests/policies/policies_composition_test.ts b/tests/policies/policies_composition_test.ts new file mode 100644 index 0000000000..daa1869d86 --- /dev/null +++ b/tests/policies/policies_composition_test.ts @@ -0,0 +1,252 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +import { gql, Meta } from "../utils/mod.ts"; + +Meta.test("Basic composition through traversal spec", async (t) => { + const e = await t.engine("policies/policies_composition.py"); + + await t.should("allow child if parent is set to 'ALLOW'", async () => { + await gql` + query { + simple_traversal_comp(one: { two: 2, three: "three" }) { # pass + one { + two # deny + three # allow + } #! allow + } + } + ` + .withContext({ + control_value: "ALLOW" + }) + .expectData({ + simple_traversal_comp: { + one: { + two: 2, + three: "three" + } + }, + }) + .on(e); + }); + + + await t.should("skip parent and go further with 'PASS'", async () => { + await gql` + query { + simple_traversal_comp(one: { two: 2, three: "three" }) { # pass + one { + two # deny + three # allow + } #! pass + } + } + ` + .withContext({ + control_value: "PASS" + }) + .expectErrorContains("'.simple_traversal_comp.one.two'") + .on(e); + }); + + + await t.should("deny and stop if parent is set to 'DENY'", async () => { + await gql` + query { + simple_traversal_comp(one: { two: 2, three: "three" }) { # pass + one { + two # deny + three # allow + } #! deny + } + } + ` + .withContext({ + control_value: "DENY" + }) + .expectErrorContains("'.simple_traversal_comp.one'") + .on(e); + }); +}); + + + +Meta.test("Basic chain composition on a single field spec", async (t) => { + const e = await t.engine("policies/policies_composition.py"); + + await t.should("have PASS not affecting the outcome (version 1)", async () => { + await gql` + query { + single_field_comp(abc: "enter" ) { + abc + } + } + ` + .withContext({ + A: "PASS", + B: "ALLOW", + C: "PASS" + }) + .expectData({ + single_field_comp: { + abc: "enter" + } + }) + .on(e); + }); + + + await t.should("have PASS not affecting the outcome (version 2)", async () => { + await gql` + query { + single_field_comp(abc: "enter" ) { + abc + } + } + ` + .withContext({ + A: "PASS", + B: "DENY", + C: "PASS" + }) + .expectErrorContains("Authorization failed for policy 'B'") + .on(e); + }); + + + await t.should("have DENY ruling", async () => { + await gql` + query { + single_field_comp(abc: "enter" ) { + abc + } + } + ` + .withContext({ + A: "PASS", + B: "DENY", + C: "ALLOW" + }) + .expectErrorContains("Authorization failed for policy 'B'") + .on(e); + }); +}); + + +Meta.test("Traversal composition on a per effect policy setup", async (t) => { + const e = await t.engine("policies/policies_composition.py"); + + const inputA = { + two: { + three: { + a: 1, + } + } + }; + + const inputB = { + two: { + three: { + b: { + c: 2 + }, + } + } + }; + + await t.should("have PASS acting as a no-op upon traversal (version 1)", async () => { + await gql` + mutation { + traversal_comp(one: $one) { + one { + two { + three { + ... on First { a } + ... on Second { + b { + c # d4 + } + } + } # d3 + } # d2 + } # d1 + } + } + ` + .withVars({ one: inputA }) + .withContext({ + depth_1: "PASS", + depth_2: "ALLOW", + depth_3: "PASS", + depth_4: "PASS" + }) + .expectData({ + traversal_comp: { + one: inputA + } + }) + .on(e); + }); + + await t.should("have PASS acting as a no-op upon traversal (version 2)", async () => { + await gql` + mutation { + traversal_comp(one: $one) { + one { + two { + three { + ... on First { a } + ... on Second { + b { + c # d4 + } + } + } # d3 + } # d2 + } # d1 + } + } + ` + .withVars({ one: inputA }) + .withContext({ + depth_1: "PASS", + depth_2: "DENY", // stop! + depth_3: "ALLOW", + depth_4: "ALLOW" + }) + .expectErrorContains("'.traversal_comp.one.two'") + .on(e); + }); + + + await t.should("DENY when a protected field on a either variant is encountered", async () => { + await gql` + mutation { + traversal_comp(one: $one) { + one { + two { + three { + ... on First { a } + ... on Second { + b { + c # d4 + } + } + } # d3 + } # d2 + } # d1 + } + } + ` + .withVars({ one: inputB }) + .withContext({ + depth_1: "PASS", + depth_2: "PASS", + depth_3: "PASS", + depth_4: "DENY" + }) + .expectErrorContains("'.traversal_comp.one.two.three$Second.b.c'") + .on(e); + }); +}); diff --git a/tests/policies/policies_test.ts b/tests/policies/policies_test.ts index 988734b6a5..b217f81381 100644 --- a/tests/policies/policies_test.ts +++ b/tests/policies/policies_test.ts @@ -29,13 +29,13 @@ Meta.test("Policies", async (t) => { await t.should("have public access", async () => { await gql` query { - pol_true(a: 1) { + pol_pass(a: 1) { a } } ` .expectData({ - pol_true: { + pol_pass: { a: 1, }, }) @@ -45,7 +45,7 @@ Meta.test("Policies", async (t) => { await t.should("have no access", async () => { await gql` query { - pol_false(a: 1) { + pol_deny(a: 1) { a } } @@ -226,7 +226,7 @@ Meta.test("Policies for effects", async (t) => { secrets: await genSecretKey(config), }); - await t.should("succeeed", async () => { + await t.should("succeed", async () => { await gql` query { findUser(id: 12) { @@ -250,6 +250,7 @@ Meta.test("Policies for effects", async (t) => { updateUser(id: 12, set: { email: "john.doe@example.com" }) { id email + password_hash # deny if role!=admin (here undefined) on effect update } } ` @@ -261,7 +262,7 @@ Meta.test("Policies for effects", async (t) => { findUser(id: 12) { id email - password_hash + password_hash # deny on effect read } } ` @@ -286,17 +287,5 @@ Meta.test("Policies for effects", async (t) => { }) .withContext({ role: "admin" }) .on(e); - - await gql` - query { - findUser(id: 12) { - id - email - password_hash - } - } - ` - .expectErrorContains("Authorization failed") - .on(e); }); }); diff --git a/tests/query_parsers/__snapshots__/query_parsers_test.ts.snap b/tests/query_parsers/__snapshots__/query_parsers_test.ts.snap index 74d5ae5d69..17f4819f82 100644 --- a/tests/query_parsers/__snapshots__/query_parsers_test.ts.snap +++ b/tests/query_parsers/__snapshots__/query_parsers_test.ts.snap @@ -4,7 +4,11 @@ snapshot[`GraphQL parser 1`] = ` [ { id: [], - policies: [], + policies: { + user: [ + 0, + ], + }, properties: { mutation: 18, query: 17, @@ -17,9 +21,15 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [ - 0, - ], + policies: { + find: [ + 0, + ], + profile: [], + update: [ + 0, + ], + }, properties: { find: 2, profile: 8, @@ -31,11 +41,8 @@ snapshot[`GraphQL parser 1`] = ` }, { input: 3, - materializer: 1, + materializer: 0, output: 5, - policies: [ - 0, - ], rate_calls: false, rate_weight: null, runtimeConfig: null, @@ -46,7 +53,9 @@ snapshot[`GraphQL parser 1`] = ` id: [ "id", ], - policies: [], + policies: { + id: [], + }, properties: { id: 4, }, @@ -55,7 +64,6 @@ snapshot[`GraphQL parser 1`] = ` type: "object", }, { - policies: [], title: "UserId", type: "string", }, @@ -63,7 +71,10 @@ snapshot[`GraphQL parser 1`] = ` id: [ "id", ], - policies: [], + policies: { + id: [], + name: [], + }, properties: { id: 4, name: 6, @@ -73,17 +84,13 @@ snapshot[`GraphQL parser 1`] = ` type: "object", }, { - policies: [], title: "User_name_string", type: "string", }, { input: 5, - materializer: 2, + materializer: 1, output: 5, - policies: [ - 0, - ], rate_calls: false, rate_weight: null, runtimeConfig: null, @@ -92,7 +99,14 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [], + policies: { + picture: [ + 0, + ], + setPicture: [ + 0, + ], + }, properties: { picture: 9, setPicture: 12, @@ -103,11 +117,8 @@ snapshot[`GraphQL parser 1`] = ` }, { input: 3, - materializer: 3, + materializer: 2, output: 10, - policies: [ - 0, - ], rate_calls: false, rate_weight: null, runtimeConfig: null, @@ -118,7 +129,10 @@ snapshot[`GraphQL parser 1`] = ` id: [ "id", ], - policies: [], + policies: { + id: [], + url: [], + }, properties: { id: 4, url: 11, @@ -129,17 +143,13 @@ snapshot[`GraphQL parser 1`] = ` }, { format: "uri", - policies: [], title: "Picture_url_string_uri", type: "string", }, { input: 10, - materializer: 4, + materializer: 3, output: 10, - policies: [ - 0, - ], rate_calls: false, rate_weight: null, runtimeConfig: null, @@ -148,9 +158,11 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [ - 0, - ], + policies: { + picture: [ + 0, + ], + }, properties: { picture: 9, }, @@ -160,9 +172,11 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [ - 0, - ], + policies: { + setPicture: [ + 0, + ], + }, properties: { setPicture: 12, }, @@ -172,7 +186,11 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [], + policies: { + find: [ + 0, + ], + }, properties: { find: 2, profile: 13, @@ -185,7 +203,11 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [], + policies: { + update: [ + 0, + ], + }, properties: { profile: 14, update: 7, @@ -198,7 +220,7 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [], + policies: {}, properties: { user: 15, }, @@ -210,7 +232,7 @@ snapshot[`GraphQL parser 1`] = ` }, { id: [], - policies: [], + policies: {}, properties: { user: 16, }, diff --git a/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap b/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap index d8d578d952..4517c2ad00 100644 --- a/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap +++ b/tests/runtimes/graphql/__snapshots__/graphql_test.ts.snap @@ -6,7 +6,6 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "graphql", - "policies": [], "properties": { "user": 1, "users": 5, @@ -21,14 +20,28 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "createUser", "create_message", "messages" - ] + ], + "policies": { + "user": [ + 0 + ], + "users": [ + 0 + ], + "createUser": [ + 0 + ], + "create_message": [ + 0 + ], + "messages": [ + 0 + ] + } }, { "type": "function", "title": "root_user_fn", - "policies": [ - 0 - ], "input": 2, "output": 4, "runtimeConfig": null, @@ -39,24 +52,24 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "root_user_fn_input", - "policies": [], "properties": { "id": 3 }, "id": [ "id" ], - "required": [] + "required": [], + "policies": { + "id": [] + } }, { "type": "string", - "title": "root_user_fn_input_id_string", - "policies": [] + "title": "root_user_fn_input_id_string" }, { "type": "object", "title": "User", - "policies": [], "properties": { "id": 3, "name": 3 @@ -64,14 +77,15 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "id": [ "id" ], - "required": [] + "required": [], + "policies": { + "id": [], + "name": [] + } }, { "type": "function", "title": "root_users_fn", - "policies": [ - 0 - ], "input": 6, "output": 7, "runtimeConfig": null, @@ -82,7 +96,6 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "root_users_fn_input", - "policies": [], "properties": {}, "id": [], "required": [] @@ -90,25 +103,23 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "root_users_fn_output", - "policies": [], "properties": { "data": 8 }, "id": [], - "required": [] + "required": [], + "policies": { + "data": [] + } }, { "type": "list", "title": "root_users_fn_output_data_User_list", - "policies": [], "items": 4 }, { "type": "function", "title": "root_createUser_fn", - "policies": [ - 0 - ], "input": 10, "output": 4, "runtimeConfig": null, @@ -119,31 +130,34 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "root_createUser_fn_input", - "policies": [], "properties": { "input": 11 }, "id": [], - "required": [] + "required": [], + "policies": { + "input": [] + } }, { "type": "object", "title": "CreateUserInput", - "policies": [], "properties": { "name": 3, "username": 3, "email": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [], + "username": [], + "email": [] + } }, { "type": "function", "title": "root_create_message_fn", - "policies": [ - 0 - ], "input": 17, "output": 20, "runtimeConfig": null, @@ -154,7 +168,6 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "message", - "policies": [], "properties": { "id": 14, "title": 3, @@ -164,17 +177,21 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "id": [ "id" ], - "required": [] + "required": [], + "policies": { + "id": [], + "title": [], + "user_id": [], + "user": [] + } }, { "type": "integer", - "title": "message_create_input_id_integer", - "policies": [] + "title": "message_create_input_id_integer" }, { "type": "function", "title": "message_output_user_fn", - "policies": [], "input": 2, "output": 16, "injections": { @@ -195,43 +212,46 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "optional", "title": "message_output_user_fn_output", - "policies": [], "item": 4, "default_value": null }, { "type": "object", "title": "root_create_message_fn_input", - "policies": [], "properties": { "data": 18 }, "id": [], - "required": [] + "required": [], + "policies": { + "data": [] + } }, { "type": "object", "title": "message_create_input", - "policies": [], "properties": { "id": 19, "title": 3, "user_id": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "title": [], + "user_id": [] + } }, { "type": "optional", "title": "message_create_input_id_message_create_input_id_integer_optional", - "policies": [], "item": 14, "default_value": null }, { "type": "object", "title": "message_output", - "policies": [], "properties": { "id": 14, "title": 3, @@ -241,14 +261,17 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "id": [ "id" ], - "required": [] + "required": [], + "policies": { + "id": [], + "title": [], + "user_id": [], + "user": [] + } }, { "type": "function", "title": "root_messages_fn", - "policies": [ - 0 - ], "input": 22, "output": 73, "runtimeConfig": null, @@ -259,7 +282,6 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "message_query_input", - "policies": [], "properties": { "where": 23, "orderBy": 54, @@ -269,19 +291,25 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "distinct": 70 }, "id": [], - "required": [] + "required": [], + "policies": { + "where": [], + "orderBy": [], + "take": [], + "skip": [], + "cursor": [], + "distinct": [] + } }, { "type": "optional", "title": "message_query_input_where_message_query_where_input_optional", - "policies": [], "item": 24, "default_value": null }, { "type": "object", "title": "message_query_where_input", - "policies": [], "properties": { "id": 25, "title": 36, @@ -291,26 +319,31 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "NOT": 23 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "title": [], + "user_id": [], + "AND": [], + "OR": [], + "NOT": [] + } }, { "type": "optional", "title": "message_query_where_input_id__prisma_integer_filter_ex_optional", - "policies": [], "item": 26, "default_value": null }, { "type": "optional", "title": "_prisma_integer_filter_ex", - "policies": [], "item": 27, "default_value": null }, { "type": "union", "title": "message_query_where_input_id_union", - "policies": [], "anyOf": [ 28, 35 @@ -319,7 +352,6 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "either", "title": "_prisma_integer_filter", - "policies": [], "oneOf": [ 14, 29, @@ -332,27 +364,30 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "_prisma_integer_filter_t1_struct", - "policies": [], "properties": { "equals": 14 }, "id": [], - "required": [] + "required": [], + "policies": { + "equals": [] + } }, { "type": "object", "title": "_prisma_integer_filter_t2_struct", - "policies": [], "properties": { "not": 14 }, "id": [], - "required": [] + "required": [], + "policies": { + "not": [] + } }, { "type": "object", "title": "_prisma_integer_filter_t3_struct", - "policies": [], "properties": { "lt": 19, "gt": 19, @@ -360,62 +395,70 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "gte": 19 }, "id": [], - "required": [] + "required": [], + "policies": { + "lt": [], + "gt": [], + "lte": [], + "gte": [] + } }, { "type": "object", "title": "_prisma_integer_filter_t4_struct", - "policies": [], "properties": { "in": 33 }, "id": [], - "required": [] + "required": [], + "policies": { + "in": [] + } }, { "type": "list", "title": "_prisma_integer_filter_t4_struct_in_message_create_input_id_integer_list", - "policies": [], "items": 14 }, { "type": "object", "title": "_prisma_integer_filter_t5_struct", - "policies": [], "properties": { "notIn": 33 }, "id": [], - "required": [] + "required": [], + "policies": { + "notIn": [] + } }, { "type": "object", "title": "message_query_where_input_id_union_t1_struct", - "policies": [], "properties": { "not": 28 }, "id": [], - "required": [] + "required": [], + "policies": { + "not": [] + } }, { "type": "optional", "title": "message_query_where_input_title__prisma_string_filter_ex_optional", - "policies": [], "item": 37, "default_value": null }, { "type": "optional", "title": "_prisma_string_filter_ex", - "policies": [], "item": 38, "default_value": null }, { "type": "union", "title": "message_query_where_input_title_union", - "policies": [], "anyOf": [ 39, 51 @@ -424,7 +467,6 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "union", "title": "_prisma_string_filter", - "policies": [], "anyOf": [ 3, 40, @@ -439,71 +481,79 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "_prisma_string_filter_t1_struct", - "policies": [], "properties": { "equals": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "equals": [] + } }, { "type": "object", "title": "_prisma_string_filter_t2_struct", - "policies": [], "properties": { "not": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "not": [] + } }, { "type": "object", "title": "_prisma_string_filter_t3_struct", - "policies": [], "properties": { "in": 43 }, "id": [], - "required": [] + "required": [], + "policies": { + "in": [] + } }, { "type": "list", "title": "_prisma_string_filter_t3_struct_in_root_user_fn_input_id_string_list", - "policies": [], "items": 3 }, { "type": "object", "title": "_prisma_string_filter_t4_struct", - "policies": [], "properties": { "notIn": 43 }, "id": [], - "required": [] + "required": [], + "policies": { + "notIn": [] + } }, { "type": "object", "title": "_prisma_string_filter_t5_struct", - "policies": [], "properties": { "contains": 3, "mode": 46 }, "id": [], - "required": [] + "required": [], + "policies": { + "contains": [], + "mode": [] + } }, { "type": "optional", "title": "_prisma_string_filter_t5_struct_mode__prisma_string_filter_t5_struct_mode_string_enum_optional", - "policies": [], "item": 47, "default_value": null }, { "type": "string", "title": "_prisma_string_filter_t5_struct_mode_string_enum", - "policies": [], "enum": [ "\\\\"insensitive\\\\"" ] @@ -511,90 +561,94 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "_prisma_string_filter_t6_struct", - "policies": [], "properties": { "search": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "search": [] + } }, { "type": "object", "title": "_prisma_string_filter_t7_struct", - "policies": [], "properties": { "startsWith": 50, "endsWith": 50 }, "id": [], - "required": [] + "required": [], + "policies": { + "startsWith": [], + "endsWith": [] + } }, { "type": "optional", "title": "_prisma_string_filter_t7_struct_startsWith_root_user_fn_input_id_string_optional", - "policies": [], "item": 3, "default_value": null }, { "type": "object", "title": "message_query_where_input_title_union_t1_struct", - "policies": [], "properties": { "not": 39 }, "id": [], - "required": [] + "required": [], + "policies": { + "not": [] + } }, { "type": "optional", "title": "message_query_where_input_AND_message_query_where_input_AND_message_query_where_input_list_optional", - "policies": [], "item": 53, "default_value": null }, { "type": "list", "title": "message_query_where_input_AND_message_query_where_input_list", - "policies": [], "items": 24 }, { "type": "optional", "title": "message_query_input_orderBy_message_order_by_optional", - "policies": [], "item": 55, "default_value": null }, { "type": "list", "title": "message_order_by", - "policies": [], "items": 56 }, { "type": "object", "title": "message_query_input_orderBy_struct", - "policies": [], "properties": { "id": 57, "title": 57, "user_id": 57 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "title": [], + "user_id": [] + } }, { "type": "optional", "title": "_prisma_sort", - "policies": [], "item": 58, "default_value": null }, { "type": "union", "title": "message_query_input_orderBy_struct_id_union", - "policies": [], "anyOf": [ 59, 60 @@ -603,17 +657,18 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "message_query_input_orderBy_struct_id_union_t0_struct", - "policies": [], "properties": { "sort": 60 }, "id": [], - "required": [] + "required": [], + "policies": { + "sort": [] + } }, { "type": "string", "title": "_prisma_sort_order", - "policies": [], "enum": [ "\\\\"asc\\\\"", "\\\\"desc\\\\"" @@ -622,40 +677,34 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "optional", "title": "message_query_input_take__take_optional", - "policies": [], "item": 62, "default_value": null }, { "type": "integer", "title": "_take", - "policies": [], "exclusiveMinimum": 0 }, { "type": "optional", "title": "message_query_input_skip__skip_optional", - "policies": [], "item": 64, "default_value": null }, { "type": "integer", "title": "_skip", - "policies": [], "minimum": 0 }, { "type": "optional", "title": "message_query_input_cursor_message_cursor_optional", - "policies": [], "item": 66, "default_value": null }, { "type": "union", "title": "message_cursor", - "policies": [], "anyOf": [ 67, 68, @@ -665,52 +714,55 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "object", "title": "message_cursor_t0_struct", - "policies": [], "properties": { "id": 14 }, "id": [ "id" ], - "required": [] + "required": [], + "policies": { + "id": [] + } }, { "type": "object", "title": "message_cursor_t1_struct", - "policies": [], "properties": { "title": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "title": [] + } }, { "type": "object", "title": "message_cursor_t2_struct", - "policies": [], "properties": { "user_id": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "user_id": [] + } }, { "type": "optional", "title": "message_query_input_distinct_message_keys_union_optional", - "policies": [], "item": 71, "default_value": null }, { "type": "list", "title": "message_keys_union", - "policies": [], "items": 72 }, { "type": "string", "title": "message_query_input_distinct_string_enum", - "policies": [], "enum": [ "\\\\"id\\\\"", "\\\\"title\\\\"", @@ -720,13 +772,11 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` { "type": "list", "title": "root_messages_fn_output", - "policies": [], "items": 74 }, { "type": "object", "title": "message_with_nested_count", - "policies": [], "properties": { "id": 14, "title": 3, @@ -734,7 +784,13 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "user": 15 }, "id": [], - "required": [] + "required": [], + "policies": { + "id": [], + "title": [], + "user_id": [], + "user": [] + } } ], "materializers": [ @@ -757,7 +813,7 @@ snapshot[`Typegraph generation with GraphQL runtime 1`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { diff --git a/tests/runtimes/grpc/__snapshots__/grpc_test.ts.snap b/tests/runtimes/grpc/__snapshots__/grpc_test.ts.snap index 2979f2aed9..c8f82853fe 100644 --- a/tests/runtimes/grpc/__snapshots__/grpc_test.ts.snap +++ b/tests/runtimes/grpc/__snapshots__/grpc_test.ts.snap @@ -7,21 +7,22 @@ snapshot[`Typegraph using grpc 1`] = ` { "type": "object", "title": "helloworld", - "policies": [], "properties": { "greet": 1 }, "id": [], "required": [ "greet" - ] + ], + "policies": { + "greet": [ + 0 + ] + } }, { "type": "function", "title": "root_greet_fn", - "policies": [ - 0 - ], "input": 2, "output": 5, "runtimeConfig": null, @@ -32,34 +33,36 @@ snapshot[`Typegraph using grpc 1`] = ` { "type": "object", "title": "root_greet_fn_input", - "policies": [], "properties": { "name": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [] + } }, { "type": "optional", "title": "root_greet_fn_input_name_root_greet_fn_input_name_string_optional", - "policies": [], "item": 4, "default_value": null }, { "type": "string", - "title": "root_greet_fn_input_name_string", - "policies": [] + "title": "root_greet_fn_input_name_string" }, { "type": "object", "title": "root_greet_fn_output", - "policies": [], "properties": { "message": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "message": [] + } } ], "materializers": [ @@ -82,7 +85,7 @@ snapshot[`Typegraph using grpc 1`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } } ], @@ -140,21 +143,22 @@ snapshot[`Typegraph using grpc 2`] = ` { "type": "object", "title": "helloworld", - "policies": [], "properties": { "greet": 1 }, "id": [], "required": [ "greet" - ] + ], + "policies": { + "greet": [ + 0 + ] + } }, { "type": "function", "title": "root_greet_fn", - "policies": [ - 0 - ], "input": 2, "output": 5, "runtimeConfig": null, @@ -165,34 +169,36 @@ snapshot[`Typegraph using grpc 2`] = ` { "type": "object", "title": "root_greet_fn_input", - "policies": [], "properties": { "name": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "name": [] + } }, { "type": "optional", "title": "root_greet_fn_input_name_root_greet_fn_input_name_string_optional", - "policies": [], "item": 4, "default_value": null }, { "type": "string", - "title": "root_greet_fn_input_name_string", - "policies": [] + "title": "root_greet_fn_input_name_string" }, { "type": "object", "title": "root_greet_fn_output", - "policies": [], "properties": { "message": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "message": [] + } } ], "materializers": [ @@ -215,7 +221,7 @@ snapshot[`Typegraph using grpc 2`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } } ], diff --git a/tests/runtimes/kv/__snapshots__/kv_test.ts.snap b/tests/runtimes/kv/__snapshots__/kv_test.ts.snap index a47e3b94e5..948e9875a4 100644 --- a/tests/runtimes/kv/__snapshots__/kv_test.ts.snap +++ b/tests/runtimes/kv/__snapshots__/kv_test.ts.snap @@ -7,7 +7,6 @@ snapshot[`Typegraph using kv 1`] = ` { "type": "object", "title": "kv", - "policies": [], "properties": { "get": 1, "set": 5, @@ -22,14 +21,28 @@ snapshot[`Typegraph using kv 1`] = ` "delete", "keys", "values" - ] + ], + "policies": { + "get": [ + 0 + ], + "set": [ + 0 + ], + "delete": [ + 0 + ], + "keys": [ + 0 + ], + "values": [ + 0 + ] + } }, { "type": "function", "title": "root_get_fn", - "policies": [ - 0 - ], "input": 2, "output": 4, "runtimeConfig": null, @@ -40,31 +53,28 @@ snapshot[`Typegraph using kv 1`] = ` { "type": "object", "title": "root_get_fn_input", - "policies": [], "properties": { "key": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "key": [] + } }, { "type": "string", - "title": "root_get_fn_input_key_string", - "policies": [] + "title": "root_get_fn_input_key_string" }, { "type": "optional", "title": "root_get_fn_output", - "policies": [], "item": 3, "default_value": null }, { "type": "function", "title": "root_set_fn", - "policies": [ - 0 - ], "input": 6, "output": 3, "runtimeConfig": null, @@ -75,20 +85,20 @@ snapshot[`Typegraph using kv 1`] = ` { "type": "object", "title": "root_set_fn_input", - "policies": [], "properties": { "key": 3, "value": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "key": [], + "value": [] + } }, { "type": "function", "title": "root_delete_fn", - "policies": [ - 0 - ], "input": 2, "output": 8, "runtimeConfig": null, @@ -98,15 +108,11 @@ snapshot[`Typegraph using kv 1`] = ` }, { "type": "integer", - "title": "root_delete_fn_output", - "policies": [] + "title": "root_delete_fn_output" }, { "type": "function", "title": "root_keys_fn", - "policies": [ - 0 - ], "input": 10, "output": 11, "runtimeConfig": null, @@ -117,25 +123,23 @@ snapshot[`Typegraph using kv 1`] = ` { "type": "object", "title": "root_keys_fn_input", - "policies": [], "properties": { "filter": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "filter": [] + } }, { "type": "list", "title": "root_keys_fn_output", - "policies": [], "items": 3 }, { "type": "function", "title": "root_values_fn", - "policies": [ - 0 - ], "input": 10, "output": 11, "runtimeConfig": null, @@ -162,7 +166,7 @@ snapshot[`Typegraph using kv 1`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { @@ -255,7 +259,6 @@ snapshot[`Typegraph using kv 2`] = ` { "type": "object", "title": "kv", - "policies": [], "properties": { "get": 1, "set": 5, @@ -270,14 +273,28 @@ snapshot[`Typegraph using kv 2`] = ` "delete", "keys", "values" - ] + ], + "policies": { + "get": [ + 0 + ], + "set": [ + 0 + ], + "delete": [ + 0 + ], + "keys": [ + 0 + ], + "values": [ + 0 + ] + } }, { "type": "function", "title": "root_get_fn", - "policies": [ - 0 - ], "input": 2, "output": 4, "runtimeConfig": null, @@ -288,31 +305,28 @@ snapshot[`Typegraph using kv 2`] = ` { "type": "object", "title": "root_get_fn_input", - "policies": [], "properties": { "key": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "key": [] + } }, { "type": "string", - "title": "root_get_fn_input_key_string", - "policies": [] + "title": "root_get_fn_input_key_string" }, { "type": "optional", "title": "root_get_fn_output", - "policies": [], "item": 3, "default_value": null }, { "type": "function", "title": "root_set_fn", - "policies": [ - 0 - ], "input": 6, "output": 3, "runtimeConfig": null, @@ -323,20 +337,20 @@ snapshot[`Typegraph using kv 2`] = ` { "type": "object", "title": "root_set_fn_input", - "policies": [], "properties": { "key": 3, "value": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "key": [], + "value": [] + } }, { "type": "function", "title": "root_delete_fn", - "policies": [ - 0 - ], "input": 2, "output": 8, "runtimeConfig": null, @@ -346,15 +360,11 @@ snapshot[`Typegraph using kv 2`] = ` }, { "type": "integer", - "title": "root_delete_fn_output", - "policies": [] + "title": "root_delete_fn_output" }, { "type": "function", "title": "root_keys_fn", - "policies": [ - 0 - ], "input": 10, "output": 11, "runtimeConfig": null, @@ -365,25 +375,23 @@ snapshot[`Typegraph using kv 2`] = ` { "type": "object", "title": "root_keys_fn_input", - "policies": [], "properties": { "filter": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "filter": [] + } }, { "type": "list", "title": "root_keys_fn_output", - "policies": [], "items": 3 }, { "type": "function", "title": "root_values_fn", - "policies": [ - 0 - ], "input": 10, "output": 11, "runtimeConfig": null, @@ -410,7 +418,7 @@ snapshot[`Typegraph using kv 2`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { diff --git a/tests/runtimes/s3/__snapshots__/s3_test.ts.snap b/tests/runtimes/s3/__snapshots__/s3_test.ts.snap index 7ffa4caed7..48f5b3fdc3 100644 --- a/tests/runtimes/s3/__snapshots__/s3_test.ts.snap +++ b/tests/runtimes/s3/__snapshots__/s3_test.ts.snap @@ -6,7 +6,6 @@ snapshot[`s3 typegraphs 1`] = ` { "type": "object", "title": "s3", - "policies": [], "properties": { "listObjects": 1, "getDownloadUrl": 10, @@ -21,14 +20,28 @@ snapshot[`s3 typegraphs 1`] = ` "signTextUploadUrl", "upload", "uploadMany" - ] + ], + "policies": { + "listObjects": [ + 0 + ], + "getDownloadUrl": [ + 0 + ], + "signTextUploadUrl": [ + 0 + ], + "upload": [ + 0 + ], + "uploadMany": [ + 0 + ] + } }, { "type": "function", "title": "root_listObjects_fn", - "policies": [ - 0 - ], "input": 2, "output": 5, "runtimeConfig": null, @@ -39,70 +52,70 @@ snapshot[`s3 typegraphs 1`] = ` { "type": "object", "title": "root_listObjects_fn_input", - "policies": [], "properties": { "path": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "path": [] + } }, { "type": "optional", "title": "root_listObjects_fn_input_path_root_listObjects_fn_input_path_string_optional", - "policies": [], "item": 4, "default_value": null }, { "type": "string", - "title": "root_listObjects_fn_input_path_string", - "policies": [] + "title": "root_listObjects_fn_input_path_string" }, { "type": "object", "title": "root_listObjects_fn_output", - "policies": [], "properties": { "keys": 6, "prefix": 9 }, "id": [], - "required": [] + "required": [], + "policies": { + "keys": [], + "prefix": [] + } }, { "type": "list", "title": "root_listObjects_fn_output_keys_root_listObjects_fn_output_keys_struct_list", - "policies": [], "items": 7 }, { "type": "object", "title": "root_listObjects_fn_output_keys_struct", - "policies": [], "properties": { "key": 4, "size": 8 }, "id": [], - "required": [] + "required": [], + "policies": { + "key": [], + "size": [] + } }, { "type": "integer", - "title": "root_listObjects_fn_output_keys_struct_size_integer", - "policies": [] + "title": "root_listObjects_fn_output_keys_struct_size_integer" }, { "type": "list", "title": "root_listObjects_fn_output_prefix_root_listObjects_fn_input_path_string_list", - "policies": [], "items": 4 }, { "type": "function", "title": "root_getDownloadUrl_fn", - "policies": [ - 0 - ], "input": 11, "output": 12, "runtimeConfig": null, @@ -113,25 +126,23 @@ snapshot[`s3 typegraphs 1`] = ` { "type": "object", "title": "root_getDownloadUrl_fn_input", - "policies": [], "properties": { "path": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "path": [] + } }, { "type": "string", "title": "root_getDownloadUrl_fn_output", - "policies": [], "format": "uri" }, { "type": "function", "title": "root_signTextUploadUrl_fn", - "policies": [ - 0 - ], "input": 14, "output": 12, "runtimeConfig": null, @@ -142,20 +153,20 @@ snapshot[`s3 typegraphs 1`] = ` { "type": "object", "title": "root_signTextUploadUrl_fn_input", - "policies": [], "properties": { "length": 8, "path": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "length": [], + "path": [] + } }, { "type": "function", "title": "root_upload_fn", - "policies": [ - 0 - ], "input": 16, "output": 18, "runtimeConfig": null, @@ -166,33 +177,31 @@ snapshot[`s3 typegraphs 1`] = ` { "type": "object", "title": "root_upload_fn_input", - "policies": [], "properties": { "file": 17, "path": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "file": [], + "path": [] + } }, { "type": "file", "title": "root_upload_fn_input_file_file", - "policies": [], "mimeTypes": [ "text/plain" ] }, { "type": "boolean", - "title": "root_upload_fn_output", - "policies": [] + "title": "root_upload_fn_output" }, { "type": "function", "title": "root_uploadMany_fn", - "policies": [ - 0 - ], "input": 20, "output": 18, "runtimeConfig": null, @@ -203,31 +212,31 @@ snapshot[`s3 typegraphs 1`] = ` { "type": "object", "title": "root_uploadMany_fn_input", - "policies": [], "properties": { "prefix": 21, "files": 22 }, "id": [], - "required": [] + "required": [], + "policies": { + "prefix": [], + "files": [] + } }, { "type": "optional", "title": "root_uploadMany_fn_input_prefix_root_listObjects_fn_input_path_string_optional", - "policies": [], "item": 4, "default_value": "" }, { "type": "list", "title": "root_uploadMany_fn_input_files_root_uploadMany_fn_input_files_file_list", - "policies": [], "items": 23 }, { "type": "file", - "title": "root_uploadMany_fn_input_files_file", - "policies": [] + "title": "root_uploadMany_fn_input_files_file" } ], "materializers": [ @@ -250,7 +259,7 @@ snapshot[`s3 typegraphs 1`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { diff --git a/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap b/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap index f2793011eb..acbebcf373 100644 --- a/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap +++ b/tests/runtimes/temporal/__snapshots__/temporal_test.ts.snap @@ -7,7 +7,6 @@ snapshot[`Typegraph using temporal 1`] = ` { "type": "object", "title": "temporal", - "policies": [], "properties": { "start": 1, "query": 6, @@ -20,14 +19,25 @@ snapshot[`Typegraph using temporal 1`] = ` "query", "signal", "describe" - ] + ], + "policies": { + "start": [ + 0 + ], + "query": [ + 0 + ], + "signal": [ + 0 + ], + "describe": [ + 0 + ] + } }, { "type": "function", "title": "root_start_fn", - "policies": [ - 0 - ], "input": 2, "output": 3, "runtimeConfig": null, @@ -38,42 +48,43 @@ snapshot[`Typegraph using temporal 1`] = ` { "type": "object", "title": "root_start_fn_input", - "policies": [], "properties": { "workflow_id": 3, "task_queue": 3, "args": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "workflow_id": [], + "task_queue": [], + "args": [] + } }, { "type": "string", - "title": "root_start_fn_input_workflow_id_string", - "policies": [] + "title": "root_start_fn_input_workflow_id_string" }, { "type": "list", "title": "root_start_fn_input_args_root_start_fn_input_args_struct_list", - "policies": [], "items": 5 }, { "type": "object", "title": "root_start_fn_input_args_struct", - "policies": [], "properties": { "some_field": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "some_field": [] + } }, { "type": "function", "title": "root_query_fn", - "policies": [ - 0 - ], "input": 7, "output": 3, "runtimeConfig": null, @@ -84,21 +95,22 @@ snapshot[`Typegraph using temporal 1`] = ` { "type": "object", "title": "root_query_fn_input", - "policies": [], "properties": { "workflow_id": 3, "run_id": 3, "args": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "workflow_id": [], + "run_id": [], + "args": [] + } }, { "type": "function", "title": "root_signal_fn", - "policies": [ - 0 - ], "input": 7, "output": 9, "injections": { @@ -118,15 +130,11 @@ snapshot[`Typegraph using temporal 1`] = ` }, { "type": "boolean", - "title": "root_signal_fn_output", - "policies": [] + "title": "root_signal_fn_output" }, { "type": "function", "title": "root_describe_fn", - "policies": [ - 0 - ], "input": 11, "output": 12, "runtimeConfig": null, @@ -137,37 +145,42 @@ snapshot[`Typegraph using temporal 1`] = ` { "type": "object", "title": "root_describe_fn_input", - "policies": [], "properties": { "workflow_id": 3, "run_id": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "workflow_id": [], + "run_id": [] + } }, { "type": "object", "title": "root_describe_fn_output", - "policies": [], "properties": { "start_time": 13, "close_time": 13, "state": 13 }, "id": [], - "required": [] + "required": [], + "policies": { + "start_time": [], + "close_time": [], + "state": [] + } }, { "type": "optional", "title": "root_describe_fn_output_start_time_root_describe_fn_output_start_time_integer_optional", - "policies": [], "item": 14, "default_value": null }, { "type": "integer", - "title": "root_describe_fn_output_start_time_integer", - "policies": [] + "title": "root_describe_fn_output_start_time_integer" } ], "materializers": [ @@ -190,7 +203,7 @@ snapshot[`Typegraph using temporal 1`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { @@ -280,7 +293,6 @@ snapshot[`Typegraph using temporal 2`] = ` { "type": "object", "title": "temporal", - "policies": [], "properties": { "startKv": 1, "query": 6, @@ -293,14 +305,25 @@ snapshot[`Typegraph using temporal 2`] = ` "query", "signal", "describe" - ] + ], + "policies": { + "startKv": [ + 0 + ], + "query": [ + 0 + ], + "signal": [ + 0 + ], + "describe": [ + 0 + ] + } }, { "type": "function", "title": "root_startKv_fn", - "policies": [ - 0 - ], "input": 2, "output": 3, "runtimeConfig": null, @@ -311,30 +334,31 @@ snapshot[`Typegraph using temporal 2`] = ` { "type": "object", "title": "root_startKv_fn_input", - "policies": [], "properties": { "workflow_id": 3, "task_queue": 3, "args": 4 }, "id": [], - "required": [] + "required": [], + "policies": { + "workflow_id": [], + "task_queue": [], + "args": [] + } }, { "type": "string", - "title": "root_startKv_fn_input_workflow_id_string", - "policies": [] + "title": "root_startKv_fn_input_workflow_id_string" }, { "type": "list", "title": "root_startKv_fn_input_args_root_startKv_fn_input_args_struct_list", - "policies": [], "items": 5 }, { "type": "object", "title": "root_startKv_fn_input_args_struct", - "policies": [], "properties": {}, "id": [], "required": [] @@ -342,9 +366,6 @@ snapshot[`Typegraph using temporal 2`] = ` { "type": "function", "title": "root_query_fn", - "policies": [ - 0 - ], "input": 7, "output": 9, "runtimeConfig": null, @@ -355,34 +376,33 @@ snapshot[`Typegraph using temporal 2`] = ` { "type": "object", "title": "root_query_fn_input", - "policies": [], "properties": { "workflow_id": 3, "run_id": 3, "args": 8 }, "id": [], - "required": [] + "required": [], + "policies": { + "workflow_id": [], + "run_id": [], + "args": [] + } }, { "type": "list", "title": "root_query_fn_input_args_root_startKv_fn_input_workflow_id_string_list", - "policies": [], "items": 3 }, { "type": "optional", "title": "root_query_fn_output", - "policies": [], "item": 3, "default_value": null }, { "type": "function", "title": "root_signal_fn", - "policies": [ - 0 - ], "input": 11, "output": 14, "runtimeConfig": null, @@ -393,43 +413,45 @@ snapshot[`Typegraph using temporal 2`] = ` { "type": "object", "title": "root_signal_fn_input", - "policies": [], "properties": { "workflow_id": 3, "run_id": 3, "args": 12 }, "id": [], - "required": [] + "required": [], + "policies": { + "workflow_id": [], + "run_id": [], + "args": [] + } }, { "type": "list", "title": "root_signal_fn_input_args_root_signal_fn_input_args_struct_list", - "policies": [], "items": 13 }, { "type": "object", "title": "root_signal_fn_input_args_struct", - "policies": [], "properties": { "key": 3, "value": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "key": [], + "value": [] + } }, { "type": "boolean", - "title": "root_signal_fn_output", - "policies": [] + "title": "root_signal_fn_output" }, { "type": "function", "title": "root_describe_fn", - "policies": [ - 0 - ], "input": 16, "output": 17, "runtimeConfig": null, @@ -440,37 +462,42 @@ snapshot[`Typegraph using temporal 2`] = ` { "type": "object", "title": "root_describe_fn_input", - "policies": [], "properties": { "workflow_id": 3, "run_id": 3 }, "id": [], - "required": [] + "required": [], + "policies": { + "workflow_id": [], + "run_id": [] + } }, { "type": "object", "title": "root_describe_fn_output", - "policies": [], "properties": { "start_time": 18, "close_time": 18, "state": 18 }, "id": [], - "required": [] + "required": [], + "policies": { + "start_time": [], + "close_time": [], + "state": [] + } }, { "type": "optional", "title": "root_describe_fn_output_start_time_root_describe_fn_output_start_time_integer_optional", - "policies": [], "item": 19, "default_value": null }, { "type": "integer", - "title": "root_describe_fn_output_start_time_integer", - "policies": [] + "title": "root_describe_fn_output_start_time_integer" } ], "materializers": [ @@ -493,7 +520,7 @@ snapshot[`Typegraph using temporal 2`] = ` "idempotent": true }, "data": { - "name": "true" + "name": "pass" } }, { diff --git a/tests/runtimes/typegate/__snapshots__/typegate_prisma_test.ts.snap b/tests/runtimes/typegate/__snapshots__/typegate_prisma_test.ts.snap index a184622b9b..3dfa21dad9 100644 --- a/tests/runtimes/typegate/__snapshots__/typegate_prisma_test.ts.snap +++ b/tests/runtimes/typegate/__snapshots__/typegate_prisma_test.ts.snap @@ -53,9 +53,7 @@ snapshot[`typegate: find available operations 1`] = ` enum: null, format: "date-time", optional: false, - policies: [ - '{"create":"__public"}', - ], + policies: [], title: "record_cursor_t3_struct_createdAt_string_datetime", type: "string", }, @@ -113,9 +111,7 @@ snapshot[`typegate: find available operations 1`] = ` enum: null, format: "date-time", optional: false, - policies: [ - '{"create":"__public"}', - ], + policies: [], title: "record_cursor_t3_struct_createdAt_string_datetime", type: "string", }, @@ -248,7 +244,28 @@ snapshot[`typegate: find available operations 1`] = ` enum: null, format: null, optional: false, - policies: [], + policies: [ + { + fieldName: "id", + policies: "[]", + }, + { + fieldName: "identities", + policies: "[]", + }, + { + fieldName: "email", + policies: "[]", + }, + { + fieldName: "name", + policies: "[]", + }, + { + fieldName: "messages", + policies: "[]", + }, + ], title: "users", type: "object", }, @@ -306,7 +323,28 @@ snapshot[`typegate: find available operations 1`] = ` enum: null, format: null, optional: false, - policies: [], + policies: [ + { + fieldName: "id", + policies: "[]", + }, + { + fieldName: "identities", + policies: "[]", + }, + { + fieldName: "email", + policies: "[]", + }, + { + fieldName: "name", + policies: "[]", + }, + { + fieldName: "messages", + policies: "[]", + }, + ], title: "users", type: "object", }, diff --git a/tests/runtimes/typegate/typegate_prisma_test.ts b/tests/runtimes/typegate/typegate_prisma_test.ts index 9f15a224d6..ed21acdde3 100644 --- a/tests/runtimes/typegate/typegate_prisma_test.ts +++ b/tests/runtimes/typegate/typegate_prisma_test.ts @@ -42,7 +42,10 @@ Meta.test({ enum default format - policies + policies { + fieldName + policies + } } } } diff --git a/tests/utils/bindings_test.ts b/tests/utils/bindings_test.ts index 42689515a3..627a90d09a 100644 --- a/tests/utils/bindings_test.ts +++ b/tests/utils/bindings_test.ts @@ -38,23 +38,27 @@ Deno.test("typegraphValidate", () => { { "type": "object", "title": "introspection", - "policies": [], "properties": { "__type": 1, - "__schema": 64, + "__schema": 26 }, "id": [], "required": [ "__type", - "__schema", + "__schema" ], + "policies": { + "__type": [ + 0 + ], + "__schema": [ + 0 + ] + } }, { "type": "function", "title": "func_79", - "policies": [ - 0, - ], "input": 2, "output": 4, "runtimeConfig": null,