Common fields on graphql interface type in react apollo with a graphene backend - interface

I have a python graphene-django backend with an interface and two types, let's say
interface InterfaceType {
id: ID!
name: String!
}
type Type1 implements InterfaceType {
aField: String
}
type Type2 implements InterfaceType {
anotherField: String
}
I'm able to query this from my react-apollo frontend using inline fragments:
query {
interfaceQuery {
...on Type1 {
id
name
}
...on Type1 {
id
name
}
}
}
But from what I understand it should also be possible to query the common fields simply as
query {
interfaceQuery
id
name
}
}
When I try this, however, I get the error Cannot query field "id" on type "InterfaceType". Did you mean to use an inline fragment on "Type1" or "Type2"?
I'm using an IntrospectionFragmentMatcher.
Am I misunderstanding and this kind of simple access of common fields is not possible, or is it just not implemented in either graphene or apollo?

If you're seeing that error, it's coming from the server, not Apollo. However, there's nothing specific to either Apollo or Graphene that should prevent you from querying the interface fields without a fragment.
The error is thrown because whatever schema you're using literally doesn't have that field on the provided type. Maybe you updated your schema without restarting the service, or were mistaken about what fields were actually part of the interface -- it's hard to know with only pseudocode provided.

Related

Resolving auto-generated typescript-mongodb types for GraphQL output

I'm using the typescript-mongodb plugin to graphql-codegen to generate Typescript types for pulling data from MongoDB and outputting it via GraphQL on Node.
My input GraphQL schema looks like this
type User #entity{
id: ID #id,
firstName: String #column #map(path: "first_name"),
...
The generated output Typescript types look correct
export type User = {
__typename?: 'User',
id?: Maybe<Scalars['ID']>,
firstName?: Maybe<Scalars['String']>,
...
And the corresponding DB object
export type UserDbObject = {
_id?: Maybe<String>,
first_name: Maybe<string>,
...
The problem is when actually sending back the mongo document as a UserDbObject I do not get the fields mapped in the output. I could write a custom resolver that re-maps the fields back to the User type, but that would mean I'm mapping the fields in two different places.
i.e. I do not get mapped fields from a resolver like this
userById: async(_root: any, args: QueryUserByIdArgs, _context: any) : Promise<UserDbObject> => {
const result = await connectDb().then((db) => {
return db.collection<UserDbObject>('users').findOne({'_id': args.id}).then((doc) => {
return doc;
});
})
...
return result as UserDbObject;
}
};
Is there a way to use the typescript-mongodb plugin to only have to map these fields in the schema, then use the auto-generated code to resolve them?
You can use mappers feature of codegen to map between your GraphQL types and your models types.
See:
https://graphql-code-generator.com/docs/plugins/typescript-resolvers#mappers---overwrite-parents-and-resolved-values
https://graphql-code-generator.com/docs/plugins/typescript-resolvers#mappers-object
Since all codegen plugins are independent and not linked together, you should do it manually, something like:
config:
mappers:
User: UserDbObject
This will make typescript-resolvers plugin to use UserDbObject at any time (as parent value, or as return value).
If you wish to automate this, you can either use the codegen programmatically (https://graphql-code-generator.com/docs/getting-started/programmatic-usage), or you can also create a .js file instead of .yaml file that will create the config section according to your needs.

GORM - get raw DB value for domain class properties

I'm using GORM for MongoDB in my Grails 3 web-app to manage read/writes from DB.
I have the following 2 domain classes:
class Company {
String id
}
class Team {
String id
Company company
}
For teams, their company is saved on DB as String, and with GORM I can simply use team.company to get an instance of Company domain class.
However, I need to override the getter for company, and I need the raw value for company id (as stored on DB), without GORM getting in the way and performing its magic.
Is there a way to get the raw String value?
Any help is welcome! Thanks in advance
Update (May 27)
Investigating #TaiwaneseDavidCheng suggestion, I updated my code to
class Company {
String id
}
class Team {
String id
Company company
String companyId
static mapping = {
company attr: "company" // optional
companyId attr: "company", insertable: false, updateable: false
}
}
Please note that I'm using GORM for MongoDB, which (citing the manual) tries to be as compatible as possible with GORM for Hibernate, but requires a slightly different implementation.
However I found out (by trial&error) that GORM for MongoDB doesn't support a similar solution, as it seems only one property at a time can be mapped to a MongoDB document property.
In particular the last property in alphabetical order wins, e.g. companyId in my example.
I figured out a way to make the whole thing work, I'm posting my own answer below.
given a non-insertable non-updateable column "companyId" in domain class
class Company {
String id
}
class Team {
String id
Company company
Long companyId
static mapping = {
company column:"companyId"
companyId column:"companyId",insertable: false,updateable: false
}
}
(Follows the edit to my question above)
I defined a custom mapping, and made use of Grails transients by also defining custom getter and setter for team's company.
class Company {
String id
}
class Team {
String id
Company company
String companyId
static mapping = {
companyId attr: "company" // match against MongoDB property
}
static transients = [ 'company' ] // non-persistent property
Company getCompany() {
return Company.get(companyId)
}
void setCompany(Company company) {
companyId = company.id
}
}

GraphQL: best way to manage mutations with interfaces?

I'am new to GraphQL but I really like it. Now that I'am playing with interfaces and unions, I'am facing a problem with mutations.
Suppose that I have this schema :
interface FoodType {
id: String
type: String
}
type Pizza implements FoodType {
id: String
type: String
pizzaType: String
toppings: [String]
size: String
}
type Salad implements FoodType {
id: String
type: String
vegetarian: Boolean
dressing: Boolean
}
type BasicFood implements FoodType {
id: String
type: String
}
Now, I'd like to create new food items, so I started doing something like this :
type Mutation {
addPizza(input:Pizza):FoodType
addSalad(input:Salad):FoodType
addBasic(input:BasicFood):FoodType
}
This did not work for 2 reasons :
If I want to pass an object as parameter, this one must be an "input" type. But "Pizza", "Salad" and "BasicFood" are just "type".
An input type cannot implement an interface.
So, my question is : How do you work with mutations in this context of interface without having to duplicate types too much? I'd like to avoid having a Pizza type for queries and an InputPizza type for mutations.
Thank you for your help.
Input and Output types are fundamentally different things, just because you may have ones that happen to represent a similar object (eg Pizza and PizzaInput), it's a false equivalence.
Let's say in your schema Pizza has a mandatory id field (your IDs aren't mandatory, but probably should be). It probably wouldn't make sense for PizzaInput to have an ID field -- it would be generated by the server. The point i'm making is that in anything but the simplest systems, there's going to be processing to turn user input into a fully-fledged object to return.
You just have to bite the bullet and do what feels like duplicated work, you'll see the benefits in the long run.

Using java.util.Set domain property with Grails 3.1.x and Mongo 5.0.x plugin

I'm trying to create an embedded collection in a Grails GORM Mongo domain class.
class User {
String name
Set<String> friends = []
}
I want to store a Set (a non-duplicated list) of other names of users.
When I try to save the User domain class:
new User(name: 'Bob').save(failOnError: true)
I get the error.
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for interface java.util.Set.
Changing the Set to List works fine but I don't want duplicates and don't want to have to manage that with a List.
Is there a way GORM will use the underlying Mongo $addToSet functionality.
It might be a GORM MongoDB issue. You can create an issue here with reproducing the issue.
But for now, you can do the workaround this problem using the List like this:
class User {
String name
List<String> friends = []
void removeDuplicate() {
this.friends?.unique()
}
def beforeInsert() {
removeDuplicate()
}
def beforeUpdate() {
removeDuplicate()
}
}

Breeze: Unable to locate property: XYZ on type:YYY when executing executeQuery on entity manager

I'm trying to get data from my Webapi2 Breeze controller with Entity Framework 6 and .NET 4.5.1. And get the error "unable to locate property" when I use the Where clause on a navigation property. The call is not even made to the Webapi2 Controller
If I leave out the where clause, the data is returned correctly.
The relevant part of the c# class:
public class NotificationRule {
public Guid NotificationRuleId { get; set; }
public virtual NotificationRuleSet NotificationRuleSet { get; set; }
}
The relevant part of the C# class in the navigational property NotificationRuleSet:
public class NotificationRuleSet{
public Guid NotificationRuleSetId { get; set; }
public virtual List<NotificationRule> NotificationRules { get; set; }
}
The relevant part of the C# Breeze controller:
public IQueryable<NotificationRuleSet> NotificationRuleSets()
{
return _contextProvider.Context.NotificationRuleSets;
}
public IQueryable<NotificationRule> NotificationRules()
{
return _contextProvider.Context.NotificationRules;
}
The relevant part of the Query (Typescript):
var query = breeze.EntityQuery.from("NotificationRules")
.where ("NotificationRuleSet.NotificationRuleSetId","==", this.NotificationRuleSetId)
.expand("NotificationRuleSet");
var Result = this.BreezeEntityManager
.executeQuery(query)
.then((data) => this.RefreshViewModelCallback(data))
.fail((data) => alert("Fail to retrieve data"));
If I leave the Where clause out, the data is transferred correctly as you can see in this Fiddler dump:
{
"$id": "1",
"$type": "Imp.Classes.NotificationRule, Imp",
"NotificationRuleId": "11111111-be1e-423c-ac5b-f2c689093aca",
"NotificationRuleSet": {
"$id": "2",
"$type": "Imp.Classes.NotificationRuleSet, Imp",
"NotificationRuleSetId": "11111111-1bd6-4520-9f69-381504b8e2b2",
"NotificationRules": [
{
"$ref": "1"
}
],
},
}
So I get an error that a property does not exists, but it seems to exists.
Using a Where on a non navigational property works fine.
I've read something about camelCasing but replacing NotificationRuleSet with notificationRuleSet gives the same error.
EDIT:
The solutions seems to be that NotificationRules in the Viewmodels query should start with a lowercase character, regardless wether the first character of the controllers method name is upper or lowercase .
camelCasing is most likely your issue provided both the entity and property do exist -
.where('notificationRuleSet.notificationRuleSetId', "==", this.NotificationRuleSetId)
Remember that when you are camelCasing your property names it is for the navigation property as well.
I thought I had an explanation after reviewing you interaction with PW Kad.
My guess was that the ACTUAL defaultResourceName for your NotificationRule type is "notificationRules".
Can you tell me what it is? The following expression will reveal it:
manager.metadataStore.getEntityType('NotificationRule').defaultResourceName;
Another question. You say it fails. What is the failure exception (check the payload of the response). Is it something like this?
$id: "1",
$type: "System.Web.Http.HttpError, System.Web.Http",
Message: "The query specified in the URI is not valid.",
ExceptionMessage: "A binary operator with incompatible types was detected. Found operand types 'Edm.Guid' and 'Edm.String' for operator kind 'Equal'.",
ExceptionType: "Microsoft.Data.OData.ODataException",
Here is what I was thinking. Most of the time, Breeze doesn't need to know the root type of a query when that query is sent to the server. It can simply wait for the query results and reason over the JSON to determine the type (or types) involved.
But the occasional query involves a filter comparison that is ambiguous in its data type. GUIDs are a good example. Unless breeze knows the query root type it can't know for sure if the "11111111-be1e-423c-ac5b-f2c689093aca" in "foo == '11111111-be1e-423c-ac5b-f2c689093aca'" should be a string or a GUID. A human would guess it's a GUID; Breeze is not so sure. You can be sure only if you know the datatype of the "foo" property.
Breeze will compose the query anyway. If it guesses string if produces a URL that looks like "...foo eq '11111111-be1e-423c-ac5b-f2c689093aca'..." and that will fail (for me anyway).
I thought this could be your issue.
I tried an experiment in DocCode that I thought would demonstrate it. I changed the endpoint name for an Order query to something that is NOT the Order type's defaultResourceName(which is "Orders").
As it happens, Web API doesn't care if the URL says 'orders' or 'Orders' so I can achieve my goal of confusing Breeze about the root type by pointing the query to "orders" and the query should still be routed by Web API to the proper controller GET method.
I was expecting that Breeze would compose the GUID query as a string and thus I could duplicate your issue. Here is my attempt
/*********************************************************
* Orders of Customers with Alfred's ID
* Customer is the related parent of Order
* CustomerID is a GUID
* Demonstrates "nested query", filtering on a related entity
* where the filter criteria is ambiguous (string or GUID)
* and only knowing the root query type (Order) can disambiguate.
* The 'defaultResourceName' for Order is "Orders", not "orders"
* so I expect this test to fail ... but it doesn't ...
* because Breeze disambiguated anyway.
*********************************************************/
test("orders of Customers w/ Alfred's ID (resource name is lowercased)", 2, function () {
var query = EntityQuery.from("orders")
.where("Customer.CustomerID", "==", testFns.wellKnownData.alfredsID)
.expand("Customer");
verifyQuery(newEm, query, "orders query", showOrdersToAlfred);
});
Unfortunately, the test passes!
This is the URL that Breeze sent to the server:
http://localhost:56337/breeze/Northwind/orders?$filter=Customer.CustomerID eq guid'785efa04-cbf2-4dd7-a7de-083ee17b6ad2'&$expand=Customer
DAMN Breeze (v.1.4.12) was too smart for me. It somehow figured out that my comparison value is a GUID ... despite not knowing the root type of the query.
That means I do not have an explanation for why, in your example, breeze.EntityQuery.from("notificationRules") works but breeze.EntityQuery.from("NotificationRules") does not.
Maybe I'll have another idea once you tell us the defaultResourceName AND show us the URLs that are generated (a) when it works and (b) when it does not work.