DGS Framework: Authentication is failing in nested queries - spring-boot

I have this schema:
type Query {
locations(id: String): [GQLLocation]
}
type GQLLocation {
id: ID!
name: String
description: String
parent: GQLLocation
children: [GQLLocation]
devices: [GQLDevice]
tenantId: String
numberOfLocations: Float
numberOfDevices: Float
}
type GQLDevice {
id: ID!
commNr: String
}
And this data fetcher for locations:
#DgsQuery
#PreAuthorize("hasRole('ADMIN')")
public DataFetcherResult<List<GQLLocation>> locations(#InputArgument("id") String locationId, DgsDataFetchingEnvironment dfe) {
Location children are resolved using a data loader.
Authentication is done via a request header.
Queries like these work fine:
{locations {
id, name, children {
id, name
}
}}
As soon as I try to get the children of a child it fails with this error message:
2022-10-11 08:54:19,706 ERROR [1154868] [ForkJoinPool.commonPool-worker-5] c.n.g.d.e.DefaultDataFetcherExceptionHandler [DefaultDataFetcherExceptionHandler.kt:36] Exception while executing data fetcher for /locations[7]/children[3]/children: An Authentication object was not found in the SecurityContext
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
It sounds like when resolving the children of the second level (child of a child) the authentication data in the context is lost. Am I doing anything wrong?

Related

Spring GraphQLmultiple schemas with Query per file

with Spring-GraphQl if I have following two schemas in the resources/graphql folder:
schema1:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
schema2:
type Query {
personByName(name: String): Person
}
type Person {
id: ID
firstName: String
lastName: String
}
Spring-GraphQL seems to be merging them into one GraphQL schema file and starting of Spring-Boot Graphql app ends with following error:
Caused by: graphql.schema.idl.errors.SchemaProblem: errors=['Query' type [#1:1] tried to redefine existing 'Query' type [#1:1]]
When I change it to:
schema1:
type Query {
bookById(id: ID): Book
personByName(name: String): Person
}
schema2:
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
type Person {
id: ID
firstName: String
lastName: String
}
it works perfectly good and I am able to call both queries with graphiql. How graphql spring works with multiple schemas? It seems spring-graphql merges files into one schema so multiple Query types per file breaks the app.
Thanks for answer.
Spring GraphQL is loading all schema resources under the configured location and is using TypeDefinitionRegistry::merge to create a single schema out of them.
I think that redifining any type (even the Query one) should raise an error, otherwise this could hide important issues and conflicting schema definitions. That's what GraphQL Java's TypeDefinitionRegistry is doing.
You can organize your schema files like this:
graphql/schema.graphqls
type Query {
}
// add common directives, scalars, etc
graphql/books.graphqls
extend type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
graphql/person.graphqls
extend type Query {
personByName(name: String): Person
}
type Person {
id: ID
firstName: String
lastName: String
}

AWS AppSync GraphQL subscriptions returning null values on event

New to GraphQL, Amplify, AppSync, etc, and running into an issue when attempting to subscribe to an onUpdate event.
I added the 'API' library to my Amplify project with authentication through an API key. I can successfully send a mutation (updatePurchaseOrder) request through Postman, and the subscription listener registers an update, but the data returned is null on everything besides the id of the updated record.
screenshot in the AppSync console
The status field is null here, I would expect to see the new updated value. Is that the expected behavior?
The defined type on this:
type PurchaseOrder #model #auth(rules: [ { allow: public } ] ){
id: ID!
name: String!
description: String!
user: String
time: String
file: String!
status: String
}
Schema mutations and subscriptions created from the initial type definition haven't been changed:
type PurchaseOrder {
id: ID!
name: String!
description: String!
user: String
time: String
file: String!
status: String
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
}
type Mutation {
updatePurchaseOrder(input: UpdatePurchaseOrderInput!, condition: ModelPurchaseOrderConditionInput): PurchaseOrder
}
input UpdatePurchaseOrderInput {
id: ID!
name: String
description: String
user: String
time: String
file: String
status: String
}
type Subscription {
onUpdatePurchaseOrder: PurchaseOrder
#aws_subscribe(mutations: ["updatePurchaseOrder"])
}
I tried logging to console in browser and see the same empty object returned. Figured listening through the AppSyncConsole would be less error prone, but I'm still seeing the same result.
Figured out where I went wrong... I needed to specify the response fields I wanted in the POST query (eg.:
mutation updatePurchaseOrder {
updatePurchaseOrder(input: {status: "Pending", id: "53a98236-7b64-428c-93ea-2fae5228c0ef"}) {
id
**status**
}
)
'status' was not in the query response parameters originally. The subscription response will only include fields that are part of the mutation response in the query itself.

What is a correct return type of a GraphQL resolve function?

I faced with an issue that can't resolve on my own. Let's go through it step by step to point out the problem.
I have a mutation bookAppointment which returns an Appointment object
GraphQL schema says that this object should return 4 properties: id, date, specialist, client.
To follow the GraphQL-style the specialist and client properties should be a field level resolvers
To fetch this objects I need pass specialistId to the specialist field level resolver, as well as clientId to the client field level resolver.
At this point a problem arises.
The field level resolvers of client, specialist expects that root mutation returns fields like clientId and specialistId. But GraphQL syntax and types that were generated by that syntax doesn't include this props (make sense).
How to "extend" the return type of the resolver and its interface BookAppointmentPayload to make me and TypeScript happy?
This is my GraphQL schema
type Client {
id: ID!
name: String!
}
type Specialist {
id: ID!
name: String!
}
type Appointment {
id: ID!
date: Date!
client: Client!
specialist: Specialist!
}
input BookAppointmentInput {
date: Date!
userId: ID!
specialistId: ID!
}
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment(input: BookAppointmentInput!): BookAppointmentPayload!
}
This is TypeScript representation of GraphQL schema
interface Client {
id: string
name: string
}
interface Specialist {
id: string
name: string
}
interface Appointment {
id: string
date: Date
client: Client
specialist: Specialist
}
interface BookAppointmentPayload {
appointment: Appointment
}
Here I define my resolvers objects
const resolvers = {
...
Mutation: {
bookAppointment: (parent, args, context, info): BookAppointmentPayload => {
return {
appointment: {
id: '1',
date: new Date(),
clientId: '1', // This prop doesn't exist in the TypeScript interface of Appointment, but is required for the field-level resolver of a `client` prop
specialistId: '1' // This prop doesn't exist int he TypeScript interface of Appointment, but is required for the field-level resolver of a `specialist` prop
}
}
}
},
Appointment: {
client: (parent, args, context, info) => {
// I need a clientId (e.g. args.clientId) to fetch the client object from the database
return {
id: '1',
name: 'Jhon'
}
},
specialist: (parent, args, context, info) => {
// I need a specialistId (e.g. args.specialistId) to fetch the specialist object from the database
return {
id: '1',
name: 'Jane'
}
}
}
}
Solution that come to my mind:
Create an interface which represent "actual" return type of the resolver
...
interface Apppointment {
id: string
date: Date
clientId: string // instead of `client: Client`
specialistId: string // instead of `specialist: Specialist`
}
interface BookAppointmentPayload {
appointment: Appointment
}
...
But this doesn't reflect the GraphQL type. Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
I would like to know how you're solving such issue?
I've been investigating this problem quite a lot and have come to the following conclusion.
Create an interface which represent "actual" return type of the resolver
Most of the time the return type of the resolver function (in JavaScript) doesn't match the type that was declared in the GraphQL SDL
For instance,
# GraphQL SDL
type Appointment {
id: String!
client: User!
specialist: Specialist!
}
type BookAppointmentInput { ... }
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment: (input: BookAppointmentInput!): BookAppointmentPayload!
}
interface AppointmentDatabaseEntity {
id: string
clientId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
specialistId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
}
interface BookAppointmentPayload {
appointment: AppointmentDatabaseEntity // The return type SHOULDN'T be equal to the GraphQL type (Appointment)
}
const resolvers = {
Mutatiuon: {
bookAppointment: (parent, args, context, info) => {
const appointment = { id: '1', specialistId: '1', clientId: '1' }
return {
id: appointment.id,
specialistId: appointment.specialistId, // Pass this prop to the child resolvers to fetch entire object
clientId: appointment.clientId // Pass this prop to the child resolvers to fetch entire object
}
}
},
Appointment: {
client: (parent: AppointmentDatabaseEntity, args, context, info) => {
const client = database.getClient(parent.clientId) // Fetching entire object by the property from the parent object
return {
id: client.id,
name: client.name,
email: client.email
}
},
specialist: (parent: AppointmentDatabaseEntity, args, context, info) => {
const specialist = database.getSpecialist(parent.specialistId) // Fetching entire object by the property from the parent object
return {
id: specialist.id,
name: specialist.name,
email: specialist.email
}
}
}
}
But this doesn't reflect the GraphQL type
As far as I understand it is okay
Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
Yes. I was wrong. The graphql-generator has a configuration file that can be used to replace default generated types with the types that you expect your resolvers to return. This option is called mappers.
plugins
config:
mappers:
User: ./my-models#UserDbObject # User is GraphQL object, which will be replaced with UserDbObject
Book: ./my-modelsBook # Same rule goes here
I don't want to go into details of how to configure it and use, but you can check the links that helped me to understand this
Documentation (check the mappers chapter)
Great explanation by
Jamie Barton (YouTube)
If you disagree with my conclusions or you have a better understanding of how to handle it feel free to leave a comment

Using nested arguments in GraphQL operations

I have a schema like so:
scalar Date
schema {
query: Query
}
type Query {
user(id: ID!): User
messages(userId: ID!): [ChatMessage!]!
}
type User {
id: ID!
username: String!
email: String!
}
type ChatMessage {
id: ID!
content: String!
time: Date!
user: User!
}
And I want to make an operation where you can get all messages for a user, but since User and ChatMessage are in separate database tables I would need to perform two queries (one to get the ChatMessages and one to get the User), so I thought I should model it like so:
query findMessagesForUser($userId: ID!) {
messages(userId: $userId) {
id
content
user(id: $userId) {
username
email
}
}
}
This returns a parse error on the schema:
GraphQLDocumentError: Unknown argument "id" on field "ChatMessage.user".
So, how do I get the $userId argument passed to the resolver for ChatMessage.user?
In your schema, you’ve defined an id input on your Query.user method. In your query, you are trying to supply an id to the Message.user property, however you haven't defined this input in your schema.
If you wanted to accept an id on ChatMessage.user, you'd need to define it as:
type ChatMessage {
id: ID!
content: String!
time: Date!
user(id: ID!): User
}
However, it wouldn't really make sense (at least to me) to construct a schema this way, I assume there's only one user (author) per message.
As indicated by #xadm, the object you resolved at the ChatMessage level will be passed into the user resolver as the first argument.
Even if you're not exposing ChatMessage.userId in the schema (that's fine), you'd still probably load this up in your back- end (the foreign key value in the ChatMessage table) and set this on the object used to resolve ChatMessage.
This way, you'll (lazy) load user IF that's included in the query, using the userId property of the parent ChatMessage object argument (remember you don't need to expose ChatMessage.userId via the schema, it's just on the object you use to resolve ChatMessage).
I'd consider modelling more like this (filter input used as an additional contrived example):
type Query {
user(id: ID!): User
messages(filter: MessageFilter): [ChatMessage!]!
}
type MessageFilter {
search: String
paging: PagingFilter
}
type PagingFilter {
after: ID!
pageSize: Int!
}
type User {
id: ID!
username: String!
email: String!
messages(filter: MessageFilter): [ChatMessage!]!
}
In your resolver map, you can wire up the same function to resolve messages at the User level and at the Query level. The only difference is you wouldn't have a userId at the Query level.
If consumers want to view/search messages from all users, they use the top level Query messages method.
{
messages({search: 'graphql'}) {
id,
content,
time
}
}
If the consumer wants to view/search one user's messages, go through the top level Query users method into messages.
{
user(id: 3) {
messages({search: 'graphql'}) {
id,
content,
time
}
}
}
The filter example is contrived, but could provide basic paging for loading of messages.
apollographql.com/docs/graphql-tools/resolvers

Datafetcher for nested types in graphql

I am using graphql and graphql-java for a simple API that fetches projects and its assignee
schema {
query: Query
}
type Query {
projects: [Project]
users: [User]
}
type Project {
id: ID!
name: String
assignee : User
status: String
}
type User {
id: ID!
name: String
}
I already have following resolvers:
private RuntimeWiring buildRunTimeWiring() {
return RuntimeWiring.newRuntimeWiring()
.type("Query", typeWiring -> typeWiring
.dataFetcher("projects", allProjectsDataFetcher)
.dataFetcher("users", allUserDataFetcher)
).build();
}
How should I write my resolvers so that when I query
projects {
id
name
assignee
}
it should return projects and its assignees. Is it even possible ?
Not sure about your resolver code (which looks good to me) and hope its resolves Project and associated assignee inside that object, I see problem in your graphQL schema (if you are expecting list of users). It should look like ...
type Project {
id: ID!
name: String
assignee : [User]
status: String
}
Make sure you return Object which should look like
{id:1, name:"someName",status:"Active", assignee :[{id:003, name:"A"}, {id:004, name:"B"}]}

Resources