From 2b3dbb46de10661e6380d22a5ee51b0b69e18c92 Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Fri, 1 May 2026 00:07:11 +0200 Subject: [PATCH] chore: upgrade Juniper to 0.17 --- Cargo.lock | 202 +++++++------- Cargo.toml | 2 +- app/src/infra/graphql.rs | 3 +- crates/graphql-server/src/api.rs | 4 +- crates/graphql-server/src/mutation/mod.rs | 8 +- crates/graphql-server/src/query/attribute.rs | 2 +- crates/graphql-server/src/query/mod.rs | 31 ++- crates/graphql-server/src/query/user.rs | 6 +- schema.graphql | 261 ++++++++++--------- server/src/graphql_server.rs | 4 +- server/tests/common/graphql.rs | 3 +- 11 files changed, 275 insertions(+), 251 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65b9ea5..8b2e754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,6 +388,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arcstr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" + [[package]] name = "arrayref" version = "0.3.9" @@ -400,12 +406,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "ascii" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" - [[package]] name = "asn1-rs" version = "0.7.1" @@ -511,6 +511,18 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "auto_enums" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65398a2893f41bce5c9259f6e1a4f03fbae40637c1bdc755b4f387f48c613b03" +dependencies = [ + "derive_utils", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -702,6 +714,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.61" @@ -806,19 +827,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "combine" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" -dependencies = [ - "ascii", - "byteorder", - "either", - "memchr", - "unreachable", -] - [[package]] name = "combine" version = "4.6.7" @@ -829,6 +837,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1216,13 +1238,13 @@ dependencies = [ [[package]] name = "derive_utils" -version = "0.11.2" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532b4c15dccee12c7044f1fcad956e98410860b22231e44a3b827464797ca7bf" +checksum = "362f47930db19fe7735f527e6595e4900316b893ebf6d48ad3d31be928d57dd6" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -1531,17 +1553,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" -[[package]] -name = "futures-enum" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888490b0e2e137dd4c1fae622efe7160e484e5fd0cc47c2a880e4614d90e48cc" -dependencies = [ - "derive_utils", - "quote", - "syn 1.0.109", -] - [[package]] name = "futures-executor" version = "0.3.32" @@ -1870,23 +1881,13 @@ dependencies = [ "serde", ] -[[package]] -name = "graphql-parser" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1abd4ce5247dfc04a03ccde70f87a048458c9356c7e41d21ad8c407b3dde6f2" -dependencies = [ - "combine 3.8.1", - "thiserror 1.0.69", -] - [[package]] name = "graphql-parser" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a818c0d883d7c0801df27be910917750932be279c7bc82dc541b8769425f409" dependencies = [ - "combine 4.6.7", + "combine", "thiserror 1.0.69", ] @@ -1909,7 +1910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f290ecfa3bea3e8a157899dc8a1d96ee7dd6405c18c8ddd213fc58939d18a0e9" dependencies = [ "graphql-introspection-query", - "graphql-parser 0.4.1", + "graphql-parser", "heck 0.4.1", "lazy_static", "proc-macro2", @@ -2333,7 +2334,6 @@ checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", "hashbrown 0.9.1", - "serde", ] [[package]] @@ -2416,35 +2416,41 @@ dependencies = [ [[package]] name = "juniper" -version = "0.15.12" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875dca5a0c08b1521e1bb0ed940e9955a9f38971008aaa2a9f64a2ac6b59e1b5" +checksum = "118749c965e171dcccffb7ca0dff6ab00451b08ba2710914caaaebc3686a7c3f" dependencies = [ + "arcstr", "async-trait", + "auto_enums", "chrono", + "compact_str", + "derive_more", "fnv", "futures", - "futures-enum", - "graphql-parser 0.3.0", - "indexmap 1.6.2", + "graphql-parser", + "indexmap 2.14.0", + "itertools", "juniper_codegen", + "ref-cast", "serde", - "smartstring", "static_assertions", "url", - "uuid 0.8.2", + "uuid", + "void", ] [[package]] name = "juniper_codegen" -version = "0.15.9" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aee97671061ad50301ba077d054d295e01d31a1868fbd07902db651f987e71db" +checksum = "8634f500d6d2ec5c91c115b83e15d998d9ea05645aaa43f7afec09e660c483ba" dependencies = [ - "proc-macro-error", + "derive_more", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.117", + "url", ] [[package]] @@ -2536,7 +2542,7 @@ dependencies = [ "thiserror 2.0.18", "tokio-util", "tracing", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2703,7 +2709,7 @@ dependencies = [ "tracing-subscriber", "url", "urlencoding", - "uuid 1.23.1", + "uuid", "webpki-roots 1.0.7", ] @@ -2770,7 +2776,7 @@ dependencies = [ "serde", "sha2 0.9.9", "thiserror 2.0.18", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2791,7 +2797,7 @@ dependencies = [ "serde", "serde_bytes", "strum 0.28.0", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2809,7 +2815,7 @@ dependencies = [ "pretty_assertions", "serde", "serde_bytes", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2828,7 +2834,7 @@ dependencies = [ "serde", "serde_bytes", "thiserror 2.0.18", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2861,7 +2867,7 @@ dependencies = [ "tokio", "tracing", "urlencoding", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2885,7 +2891,7 @@ dependencies = [ "rand 0.8.6", "tokio", "tracing", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2959,7 +2965,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -2975,7 +2981,7 @@ dependencies = [ "lldap_opaque_handler", "mockall", "tracing", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -3780,6 +3786,26 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "regex" version = "1.12.3" @@ -4144,7 +4170,7 @@ dependencies = [ "thiserror 2.0.18", "tracing", "url", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -4170,7 +4196,7 @@ dependencies = [ "chrono", "inherent", "ordered-float", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -4182,7 +4208,7 @@ dependencies = [ "chrono", "sea-query", "sqlx", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -4482,17 +4508,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - [[package]] name = "smawk" version = "0.3.2" @@ -4585,7 +4600,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "uuid 1.23.1", + "uuid", "webpki-roots 0.26.11", ] @@ -4667,7 +4682,7 @@ dependencies = [ "stringprep", "thiserror 2.0.18", "tracing", - "uuid 1.23.1", + "uuid", "whoami", ] @@ -4706,7 +4721,7 @@ dependencies = [ "stringprep", "thiserror 2.0.18", "tracing", - "uuid 1.23.1", + "uuid", "whoami", ] @@ -4733,7 +4748,7 @@ dependencies = [ "thiserror 2.0.18", "tracing", "url", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -5134,7 +5149,7 @@ dependencies = [ "mutually_exclusive_features", "pin-project", "tracing", - "uuid 1.23.1", + "uuid", ] [[package]] @@ -5279,15 +5294,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] - [[package]] name = "untrusted" version = "0.9.0" @@ -5334,12 +5340,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "uuid" version = "1.23.1" diff --git a/Cargo.toml b/Cargo.toml index d11f1b7..7adf36d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ features = ["jpeg"] version = "0.14" [workspace.dependencies.juniper] -version = "0.15" +version = "0.17" default-features = false features = ["chrono", "schema-language", "url", "uuid"] diff --git a/app/src/infra/graphql.rs b/app/src/infra/graphql.rs index 5556110..58d0ea4 100644 --- a/app/src/infra/graphql.rs +++ b/app/src/infra/graphql.rs @@ -1 +1,2 @@ -pub type DateTimeUtc = chrono::DateTime; +pub type DateTime = chrono::DateTime; +pub type DateTimeUtc = DateTime; diff --git a/crates/graphql-server/src/api.rs b/crates/graphql-server/src/api.rs index 48ff295..c2b6051 100644 --- a/crates/graphql-server/src/api.rs +++ b/crates/graphql-server/src/api.rs @@ -60,7 +60,7 @@ impl Context { impl juniper::Context for Context {} type Schema = - RootNode<'static, Query, Mutation, EmptySubscription>>; + RootNode, Mutation, EmptySubscription>>; pub fn schema() -> Schema { Schema::new( @@ -73,7 +73,7 @@ pub fn schema() -> Schema { pub fn export_schema(output_file: Option) -> anyhow::Result<()> { use anyhow::Context; use lldap_sql_backend_handler::SqlBackendHandler; - let output = schema::().as_schema_language(); + let output = schema::().as_sdl(); match output_file { None => println!("{output}"), Some(path) => { diff --git a/crates/graphql-server/src/mutation/mod.rs b/crates/graphql-server/src/mutation/mod.rs index 471b610..d1cfd8c 100644 --- a/crates/graphql-server/src/mutation/mod.rs +++ b/crates/graphql-server/src/mutation/mod.rs @@ -561,13 +561,13 @@ mod tests { use mockall::predicate::eq; use pretty_assertions::assert_eq; - fn mutation_schema<'q, C, Q, M>( + fn mutation_schema( query_root: Q, mutation_root: M, - ) -> RootNode<'q, Q, M, EmptySubscription> + ) -> RootNode> where - Q: GraphQLType + 'q, - M: GraphQLType + 'q, + Q: GraphQLType, + M: GraphQLType, { RootNode::new(query_root, mutation_root, EmptySubscription::::new()) } diff --git a/crates/graphql-server/src/query/attribute.rs b/crates/graphql-server/src/query/attribute.rs index 855fb18..bc0995f 100644 --- a/crates/graphql-server/src/query/attribute.rs +++ b/crates/graphql-server/src/query/attribute.rs @@ -93,7 +93,7 @@ impl AttributeValue { } } - pub(super) fn name(&self) -> &str { + pub(super) fn attribute_name(&self) -> &str { self.attribute.name.as_str() } } diff --git a/crates/graphql-server/src/query/mod.rs b/crates/graphql-server/src/query/mod.rs index ef159c3..a83ea12 100644 --- a/crates/graphql-server/src/query/mod.rs +++ b/crates/graphql-server/src/query/mod.rs @@ -47,7 +47,11 @@ impl Query { "1.0" } - pub async fn user(context: &Context, user_id: String) -> FieldResult> { + pub async fn user( + &self, + context: &Context, + user_id: String, + ) -> FieldResult> { use anyhow::Context; let span = debug_span!("[GraphQL query] user"); span.in_scope(|| { @@ -61,14 +65,15 @@ impl Query { &span, "Unauthorized access to user data", ))?; - let schema = Arc::new(self.get_schema(context, span.clone()).await?); + let schema: Arc = Arc::new(self.get_schema(context, span.clone()).await?); let user = handler.get_user_details(&user_id).instrument(span).await?; User::::from_user(user, schema) } async fn users( + &self, context: &Context, - #[graphql(name = "where")] filters: Option, + filters: Option, ) -> FieldResult>> { let span = debug_span!("[GraphQL query] users"); span.in_scope(|| { @@ -80,7 +85,7 @@ impl Query { &span, "Unauthorized access to user list", ))?; - let schema = Arc::new(self.get_schema(context, span.clone()).await?); + let schema: Arc = Arc::new(self.get_schema(context, span.clone()).await?); let users = handler .list_users( filters @@ -96,7 +101,7 @@ impl Query { .collect() } - async fn groups(context: &Context) -> FieldResult>> { + async fn groups(&self, context: &Context) -> FieldResult>> { let span = debug_span!("[GraphQL query] groups"); let handler = context .get_readonly_handler() @@ -104,7 +109,7 @@ impl Query { &span, "Unauthorized access to group list", ))?; - let schema = Arc::new(self.get_schema(context, span.clone()).await?); + let schema: Arc = Arc::new(self.get_schema(context, span.clone()).await?); let domain_groups = handler.list_groups(None).instrument(span).await?; domain_groups .into_iter() @@ -112,7 +117,11 @@ impl Query { .collect() } - async fn group(context: &Context, group_id: i32) -> FieldResult> { + async fn group( + &self, + context: &Context, + group_id: i32, + ) -> FieldResult> { let span = debug_span!("[GraphQL query] group"); span.in_scope(|| { debug!(?group_id); @@ -123,7 +132,7 @@ impl Query { &span, "Unauthorized access to group data", ))?; - let schema = Arc::new(self.get_schema(context, span.clone()).await?); + let schema: Arc = Arc::new(self.get_schema(context, span.clone()).await?); let group_details = handler .get_group_details(GroupId(group_id)) .instrument(span) @@ -131,7 +140,7 @@ impl Query { Group::::from_group_details(group_details, schema.clone()) } - async fn schema(context: &Context) -> FieldResult> { + async fn schema(&self, context: &Context) -> FieldResult> { let span = debug_span!("[GraphQL query] get_schema"); self.get_schema(context, span).await.map(Into::into) } @@ -175,9 +184,9 @@ mod tests { use pretty_assertions::assert_eq; use std::collections::HashSet; - fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> + fn schema(query_root: Q) -> RootNode, EmptySubscription> where - Q: GraphQLType + 'q, + Q: GraphQLType, { RootNode::new( query_root, diff --git a/crates/graphql-server/src/query/user.rs b/crates/graphql-server/src/query/user.rs index ca497c7..5273b58 100644 --- a/crates/graphql-server/src/query/user.rs +++ b/crates/graphql-server/src/query/user.rs @@ -70,7 +70,7 @@ impl User { fn first_name(&self) -> &str { self.attributes .iter() - .find(|a| a.name() == "first_name") + .find(|a| a.attribute_name() == "first_name") .map(|a| a.attribute.value.as_str().unwrap_or_default()) .unwrap_or_default() } @@ -78,7 +78,7 @@ impl User { fn last_name(&self) -> &str { self.attributes .iter() - .find(|a| a.name() == "last_name") + .find(|a| a.attribute_name() == "last_name") .map(|a| a.attribute.value.as_str().unwrap_or_default()) .unwrap_or_default() } @@ -86,7 +86,7 @@ impl User { fn avatar(&self) -> Option { self.attributes .iter() - .find(|a| a.name() == "avatar") + .find(|a| a.attribute_name() == "avatar") .map(|a| { String::from( a.attribute diff --git a/schema.graphql b/schema.graphql index c9bfaba..e1bbeec 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,63 +1,33 @@ -type AttributeValue { - name: String! - value: [String!]! - schema: AttributeSchema! +schema { + query: Query + mutation: Mutation } -type Mutation { - createUser(user: CreateUserInput!): User! - createGroup(name: String!): Group! - createGroupWithDetails(request: CreateGroupInput!): Group! - updateUser(user: UpdateUserInput!): Success! - updateGroup(group: UpdateGroupInput!): Success! - addUserToGroup(userId: String!, groupId: Int!): Success! - removeUserFromGroup(userId: String!, groupId: Int!): Success! - deleteUser(userId: String!): Success! - deleteGroup(groupId: Int!): Success! - addUserAttribute(name: String!, attributeType: AttributeType!, isList: Boolean!, isVisible: Boolean!, isEditable: Boolean!): Success! - addGroupAttribute(name: String!, attributeType: AttributeType!, isList: Boolean!, isVisible: Boolean!, isEditable: Boolean!): Success! - deleteUserAttribute(name: String!): Success! - deleteGroupAttribute(name: String!): Success! - addUserObjectClass(name: String!): Success! - addGroupObjectClass(name: String!): Success! - deleteUserObjectClass(name: String!): Success! - deleteGroupObjectClass(name: String!): Success! +enum AttributeType { + STRING + INTEGER + JPEG_PHOTO + DATE_TIME } -type Group { - id: Int! +input AttributeValueInput { + """ + The name of the attribute. It must be present in the schema, and the type informs how + to interpret the values. + """ name: String! + """ + The values of the attribute. + If the attribute is not a list, the vector must contain exactly one element. + Integers (signed 64 bits) are represented as strings. + Dates are represented as strings in RFC3339 format, e.g. "2019-10-12T07:20:50.52Z". + JpegPhotos are represented as base64 encoded strings. They must be valid JPEGs. + """ value: [String!]! +} + +"The details required to create a group." +input CreateGroupInput { displayName: String! - creationDate: DateTimeUtc! - uuid: String! - "User-defined attributes." - attributes: [AttributeValue!]! - "The groups to which this user belongs." - users: [User!]! -} - -""" - A filter for requests, specifying a boolean expression based on field constraints. Only one of - the fields can be set at a time. -""" -input RequestFilter { - any: [RequestFilter!] - all: [RequestFilter!] - not: RequestFilter - eq: EqualityConstraint - memberOf: String - memberOfId: Int -} - -"DateTime" -scalar DateTimeUtc - -type Query { - apiVersion: String! - user(userId: String!): User! - users(filters: RequestFilter): [User!]! - groups: [Group!]! - group(groupId: Int!): Group! - schema: Schema! + "User-defined attributes." attributes: [AttributeValueInput!] } "The details required to create a user." @@ -80,19 +50,36 @@ input CreateUserInput { "Attributes." attributes: [AttributeValueInput!] } -type ObjectClassInfo { - objectClass: String! - isHardcoded: Boolean! +input EqualityConstraint { + field: String! + value: String! } -type AttributeSchema { - name: String! - attributeType: AttributeType! - isList: Boolean! - isVisible: Boolean! - isEditable: Boolean! - isHardcoded: Boolean! - isReadonly: Boolean! +""" + A filter for requests, specifying a boolean expression based on field constraints. Only one of + the fields can be set at a time. +""" +input RequestFilter { + any: [RequestFilter!] + all: [RequestFilter!] + not: RequestFilter + eq: EqualityConstraint + memberOf: String + memberOfId: Int +} + +"The fields that can be updated for a group." +input UpdateGroupInput { + "The group ID." id: Int! + "The new display name." displayName: String + """ + Attribute names to remove. + They are processed before insertions. + """ removeAttributes: [String!] + """ + Inserts or updates the given attributes. + For lists, the entire list must be provided. + """ insertAttributes: [AttributeValueInput!] } "The fields that can be updated for a user." @@ -122,9 +109,87 @@ input UpdateUserInput { """ insertAttributes: [AttributeValueInput!] } -input EqualityConstraint { - field: String! - value: String! +""" + Combined date and time (with time zone) in [RFC 3339][0] format. + + Represents a description of an exact instant on the time-line (such as the + instant that a user account was created). + + [`DateTime` scalar][1] compliant. + + See also [`chrono::DateTime`][2] for details. + + [0]: https://datatracker.ietf.org/doc/html/rfc3339#section-5 + [1]: https://graphql-scalars.dev/docs/scalars/date-time + [2]: https://docs.rs/chrono/latest/chrono/struct.DateTime.html +""" +scalar DateTime + +type AttributeList { + attributes: [AttributeSchema!]! + extraLdapObjectClasses: [String!]! + ldapObjectClasses: [ObjectClassInfo!]! +} + +type AttributeSchema { + name: String! + attributeType: AttributeType! + isList: Boolean! + isVisible: Boolean! + isEditable: Boolean! + isHardcoded: Boolean! + isReadonly: Boolean! +} + +type AttributeValue { + name: String! + value: [String!]! + schema: AttributeSchema! +} + +type Group { + id: Int! + displayName: String! + creationDate: DateTime! + uuid: String! + "User-defined attributes." + attributes: [AttributeValue!]! + "The groups to which this user belongs." + users: [User!]! +} + +type Mutation { + createUser(user: CreateUserInput!): User! + createGroup(name: String!): Group! + createGroupWithDetails(request: CreateGroupInput!): Group! + updateUser(user: UpdateUserInput!): Success! + updateGroup(group: UpdateGroupInput!): Success! + addUserToGroup(userId: String!, groupId: Int!): Success! + removeUserFromGroup(userId: String!, groupId: Int!): Success! + deleteUser(userId: String!): Success! + deleteGroup(groupId: Int!): Success! + addUserAttribute(name: String!, attributeType: AttributeType!, isList: Boolean!, isVisible: Boolean!, isEditable: Boolean!): Success! + addGroupAttribute(name: String!, attributeType: AttributeType!, isList: Boolean!, isVisible: Boolean!, isEditable: Boolean!): Success! + deleteUserAttribute(name: String!): Success! + deleteGroupAttribute(name: String!): Success! + addUserObjectClass(name: String!): Success! + addGroupObjectClass(name: String!): Success! + deleteUserObjectClass(name: String!): Success! + deleteGroupObjectClass(name: String!): Success! +} + +type ObjectClassInfo { + objectClass: String! + isHardcoded: Boolean! +} + +type Query { + apiVersion: String! + user(userId: String!): User! + users(filters: RequestFilter): [User!]! + groups: [Group!]! + group(groupId: Int!): Group! + schema: Schema! } type Schema { @@ -132,38 +197,8 @@ type Schema { groupSchema: AttributeList! } -"The fields that can be updated for a group." -input UpdateGroupInput { - "The group ID." id: Int! - "The new display name." displayName: String - """ - Attribute names to remove. - They are processed before insertions. - """ removeAttributes: [String!] - """ - Inserts or updates the given attributes. - For lists, the entire list must be provided. - """ insertAttributes: [AttributeValueInput!] -} - -input AttributeValueInput { - """ - The name of the attribute. It must be present in the schema, and the type informs how - to interpret the values. - """ name: String! - """ - The values of the attribute. - If the attribute is not a list, the vector must contain exactly one element. - Integers (signed 64 bits) are represented as strings. - Dates are represented as strings in RFC3339 format, e.g. "2019-10-12T07:20:50.52Z". - JpegPhotos are represented as base64 encoded strings. They must be valid JPEGs. - """ value: [String!]! -} - -"The details required to create a group." -input CreateGroupInput { - displayName: String! - "User-defined attributes." attributes: [AttributeValueInput!] +type Success { + ok: Boolean! } type User { @@ -173,32 +208,10 @@ type User { firstName: String! lastName: String! avatar: String - creationDate: DateTimeUtc! + creationDate: DateTime! uuid: String! "User-defined attributes." attributes: [AttributeValue!]! "The groups to which this user belongs." groups: [Group!]! } - -enum AttributeType { - STRING - INTEGER - JPEG_PHOTO - DATE_TIME -} - -type AttributeList { - attributes: [AttributeSchema!]! - extraLdapObjectClasses: [String!]! - ldapObjectClasses: [ObjectClassInfo!]! -} - -type Success { - ok: Boolean! -} - -schema { - query: Query - mutation: Mutation -} diff --git a/server/src/graphql_server.rs b/server/src/graphql_server.rs index 65f7f72..4596717 100644 --- a/server/src/graphql_server.rs +++ b/server/src/graphql_server.rs @@ -52,7 +52,7 @@ where /// Actix GraphQL Handler for GET requests pub async fn get_graphql_handler( - schema: &juniper::RootNode<'static, Query, Mutation, Subscription, S>, + schema: &juniper::RootNode, context: &CtxT, req: HttpRequest, ) -> Result @@ -81,7 +81,7 @@ where /// Actix GraphQL Handler for POST requests pub async fn post_graphql_handler( - schema: &juniper::RootNode<'static, Query, Mutation, Subscription, S>, + schema: &juniper::RootNode, context: &CtxT, req: HttpRequest, mut payload: actix_http::Payload, diff --git a/server/tests/common/graphql.rs b/server/tests/common/graphql.rs index b619f73..5ff60b1 100644 --- a/server/tests/common/graphql.rs +++ b/server/tests/common/graphql.rs @@ -4,7 +4,8 @@ use anyhow::{Context, Result, anyhow}; use graphql_client::GraphQLQuery; use reqwest::blocking::Client; -pub type DateTimeUtc = chrono::DateTime; +pub type DateTime = chrono::DateTime; +pub type DateTimeUtc = DateTime; #[derive(GraphQLQuery)] #[graphql(