Can’t convert {data: []} to Vector

hi @FreeRoss

would you provide the GraphQL schema and, in case. the function you are using?

Luigi

Sure. The schema:

type User 
 {  
    active : Boolean!
    username : String! @unique
    password : String! @unique
    ethaddress : String! @unique
    description : String
    email : String @unique 
    mobile : String @unique
    member_since: Int!
}

type Player 
 {  address : String! @unique
    rank : Int!
    challengeraddress : String
}

type Ranking
  { 
    active : Boolean!
    rankingname : String! @unique
    rankingdesc : String
    rankingowneraddr : String! @unique
    }

type UserJoinedRanking
  { 
    active : Boolean!
    rankingid : String!
    rankingname : String!
    useraddr : String!
    rankingowneraddr : String!
    }


type Mutation {
  createAndOrLoginUser(active: Boolean!, username: String!, password: String!, ethaddress: String!, description: String, email: String, mobile: String): String! @resolver(name: "create_andor_login_user")
  createNewPlayer(active: Boolean!, address: String!, rank: Int!, challengeraddress: String): Player! @resolver(name: "create_new_player")
  createNewRanking(active: Boolean!, rankingname : String!, rankingdesc : String, rankingowneraddr : String!): Ranking! @resolver(name: "create_new_ranking")
  createNewUser(active: Boolean!, username : String!, password : String!, ethaddress: String!, description: String, email: String, mobile: String): String! @resolver(name: "create_new_user")
  createNewUserJoinedRanking(active: Boolean!, rankingid : String!, rankingname : String!, useraddr: String!, rankingowneraddr : String!): UserJoinedRanking! @resolver(name: "create_new_userjoinedranking")
}

type Query {
  allUserNames: [String!]! @resolver(name: "all_user_names")
  allPlayerAddresses: [String!]! @resolver(name: "all_player_addresses")
  allPlayerRanks: [Int!]! @resolver(name: "all_player_ranks")
  allPlayerChallengerAddresses: [String!]! @resolver(name: "all_player_challenger_addresses")
  allPlayers: [Player] @resolver(name: "all_players")
  allRankings: [Ranking] @resolver(name: "all_rankings")
  allUserJoinedRanking: [UserJoinedRanking] @resolver(name: "all_userjoinedranking")
  allUsers: [User] @resolver(name: "all_users")
  usernameFromEthaddress(ethaddress: String!): [String!]! @resolver(name: "usernameFromEthaddress")
  loginUser(username: String!, password: String!): [String!]! @resolver(name: "login_user")
  findRankingById (rankingid: Int!): [Ranking!]! @resolver(name: "find_ranking_by_id")
}

the function FQL:

{
  name: "login_user",
  role: Role("login"),
  body: Query(
    Lambda(
      ["username", "password"],
      Let(
        {
          match: Match(Index("unique_User_username"), Var("username")),
          user: If(Exists(Var("match")), Get(Var("match")), "false"),
          login: Login(Select("ref", Var("user")), {
            password: Var("password")
          })
        },
        Select("secret", Var("login"))
      )
    )
  )
}

Role login:

{
  ref: Role("login"),
  ts: 1600844588120000,
  name: "login",
  privileges: [
    {
      resource: Collection("User"),
      actions: {
        read: true,
        write: false,
        create: false,
        delete: false,
        history_read: false,
        history_write: false,
        unrestricted_read: false
      }
    },
    {
      resource: Index("unique_User_username"),
      actions: {
        unrestricted_read: false,
        read: true
      }
    }
  ],
  membership: []
}

thanks …

Hi @FreeRoss,

would you provide the create_new_user function as well?

Thanks,

Luigi

Yes. Here:

{
  name: "create_new_user",
  role: Role("register"),
  body: Query(
    Lambda(
      [
        "active",
        "username",
        "password",
        "ethaddress",
        "description",
        "email",
        "mobile"
      ],
      Let(
        {
          user: Create(Collection("User"), {
            credentials: { password: Var("password") },
            data: {
              active: Var("active"),
              username: Var("username"),
              ethaddress: Var("ethaddress"),
              description: Var("description"),
              email: Var("email"),
              mobile: Var("mobile"),
              member_since: ToMillis(Now())
            }
          }),
          login: Login(Select("ref", Var("user")), {
            password: Var("password")
          })
        },
        Select("secret", Var("login"))
      )
    )
  )
}

Thanks …

I do not think you need to use paginated: true. You only want to use paginated: true if your FQL resolver function is returning a page (Fauna specific). That way fauna can correctly interpret your graphql schema types and during your schema upload they will actually perform a transformation on your type definitions in order to integrate it into fauna so you do not have to worry about it. This way you can define your result types as you normally would with any graphql framework and let fauna handle the fauna specific pagination types for you.

In your case I think the issue is that your FQL function is returning a String and in your graphql type definitions you’re telling your loginUser Query to expect an array of strings and the array is required. I think it should look like this.

loginUser(username: String!, password: String!): String! @resolver(name: "login_user")

Notice the change I made from [String!]! to String! on the return value. This way you can leave your FQL function returning the token as a String. The below should return your string as expected no pagination needed.

query {
  loginUser(username: "Test 1", password: "hello" )
}

If you want to have a sub selection in your Query you can do that as well! In my schema I created the below:

type LoginResult @embedded {
  token: String!
}

I use @embedded to let fauna know that this type def is not related to a collection. Then my login function I use this type instead of String!

loginUser(input: LoginUserInput): LoginResult! @resolver(name: "login_user")

Then the only change you make your FQL function is the last line.
From Select("secret", Var("login"))
To { token: Select("secret", Var("login")) }

Now your Query would look like this:

query {
  loginUser(username: "Test 1", password: "hello" )
  {
    token
  }
}

The only reason I do this is so that if one day in the future I want to return more than just a token on login I can expand my type defs and include a User type and perhaps get the user’s roles as soon as they login. It is not necessary but I wanted to show you how changing the return part of the FQL would affect the Graphql schema and query. All I did was return an object containing the token string vs only the token string itself. I hope this helps!!

1 Like

@yellowspaceboots
Thanks very much for the detailed answer, which is certainly helpful.
Frustratingly, my schema now won’t update (it did yesterday, from terminal within VSCode, at least). I cleared the browser cache, but that hasn’t resolved it.
I’m therefore unable to implement the small change to the schema you recommended above, which will, I expect, fix the immediate issue.
Assuming it does, thanks again, and your other points are noted.

@yellowspaceboots. I was asked by Jay to create a new db to test your solution. I did and it works. Thank you. This issue is solved and it is now a separate schema upload issue which we are resolving in another thread:
Instance data is not valid

Thanks for having the foresight to post that. I updated my schema with @embedded as you mention as I need to receive a User on login:

query LoginUser {
  loginUser(username: "Test 1", password: "hello") {
    token
    user {
      username
    }
  }
}

however, this gives me:

{
  "errors": [
    {
      "message": "Ref or Set expected, Object provided.",
      "extensions": {
        "code": "invalid argument"
      }
    }
  ]
}

in PlayGround.
How should I fix this? thanks …

Hi @FreeRoss,

Seems that your resolver returns an object but a Ref is expected.
You should check such function.

Luigi

Hi @Luigi_Servini.
Since User is an object and I want a token and an object I looked at:

A UDF that returns an embedded object in the docs.

The example has a function which returns 2 fields:

Query(Lambda([], {
time: Time("now"),
sample: true
}))

My UDF returns two fields:

Query(
Lambda(
["username", "password"],
Let(
{
match: Match(Index("unique_User_username"), Var("username")),
user: If(Exists(Var("match")), Get(Var("match")), "false"),
login: Login(Select("ref", Var("user")), { password: Var("password") })
},
{ token: Select("secret", Var("login")), user: Var("user") }
)
)
)

token and user
The example has:

type SampleObj @embedded {
time: Time!
sample: Boolean!
}
type Query {
sampleObj: SampleObj! @resolver(name: "sample_obj")
}

I have:

type LoginResult @embedded {
token: String!
user: User
}
type Query
{
loginUser(username: String!, password: String!): LoginResult! @resolver(name: "login_user")
}

In PG:

query LoginUser {
  loginUser(username: "Test 1", password: "hello"){
      token
      user {
        username
    }
  }
}

gives me:
"Ref or Set expected, Object provided.",

Since the example is to return an embedded object:

  1. Why is a Ref expected?
  2. What should I change in the function so that an object is expected and returned?
    Thanks …

FYI: I have changed the schema slightly:

  loginUser(username: String!, password: String!): LoginResult @resolver(name: "login_user")
  findRankingById (rankingid: Int!): [Ranking!]! @resolver(name: "find_ranking_by_id")
  findPlayersByRankingId (rankingid: String!): [Player!]! @resolver(name: "find_players_by_ranking_id")
}

type LoginResult @embedded {
  token: String
  user: User
}

But I get the same error which I am unable to resolve in PG currently:
“Ref or Set expected, Object provided.”

This may be happening because your User type is not an embedded type. Even though you have LoginResult with @embedded you are also referencing a User type which I bet is not embedded. What you may need to do then is create another UserEmbedded @embedded type with fields you want return on login and then in your FQL you might have to use `Select()’ to get the data object from the User result

@yellowspaceboots
Thanks for your response. If I understood correctly I have updated the schema with:

type LoginResult @embedded {
  token: String
  user: UserEmbedded
}

type UserEmbedded @embedded {
  active : Boolean!
  username : String! @unique
  password : String! @unique
  description : String
  email : String @unique 
  mobile : String @unique
  member_since: Int!
}

I am unable to actually test this change (or any others to the schema) due to the ‘instance data’ issue
However, once that is hopefully finally resolved I will immediately try out this change. It seems a bit cumbersome to effectively be defining User twice to perform the same operations. Any comments you have on that point, as well as any modifications you think I may need to make to the above schema change would be helpful … thanks again …

@FreeRoss I replied to your upload issue here. Hopefully that helps you. As far as your UserEmbedded type goes. Just so you know I have been using Fauna’s @embedded directive as a way to shut off Fauna’s autogeneration. If you use @embedded Fauna tends to leave that type alone and does not generate any CRUD operations or return Types. That also means that inside of an embedded type like this, Fauna’s other directives are basically N/A. You will have to remove the @unique directives because they will no longer function as expected inside a @embedded type. Also since no Page return type has been generated you must make sure that you are only returning the data object for your user field in your FQL or you will get an error similar to the one you had before.

@yellowspaceboots
I responded to the ‘Instance data’ issue separately.
There has definitely been progress.
I now get the following error:

{
  "data": {
    "loginUser": {
      "token": "fnED7GeP9PACBQPk7MnXsAYMoG4XAjU-i8t4nlq63KwVv6CfBNA",
      "logginUser": null
    }
  },
  "errors": [
    {
      "message": "Cannot return null for non-nullable type (line 5, column 7):\n      username\n      ^",
      "path": [
        "loginUser",
        "logginUser",
        "username"
      ],
      "locations": [
        {
          "line": 5,
          "column": 7
        }
      ]
    }
  ]
}

There is a user in collection User with username “Test 1”. These are the relevant sections, I believe.
In Schema:

type User @embedded
 {  
    active : Boolean!
    username : String!
    password : String!
    description : String
    email : String 
    mobile : String
    member_since: Int!
}
type Query {
  loginUser(username: String!, password: String!): loginResult! @resolver(name: "login_user")
}

type loginResult @embedded
{
  token : String
  logginUser : User 
}

In PG:

query {
  loginUser(username: "Test 1", password: "hello"){
    token
    logginUser{username}
  }
}

In roles:

{
  ref: Role("bootstrap"),
  ts: 1605876630216000,
  name: "bootstrap",
  privileges: [
    {
      resource: Ref(Ref("functions"), "login_user"),
      actions: {
        call: true
      }
    },
    {
      resource: Ref(Ref("functions"), "create_new_user"),
      actions: {
        call: true
      }
    },
    {
      resource: Ref(Ref("functions"), "all_users"),
      actions: {
        call: true
      }
    },
    {
      resource: Ref(Ref("functions"), "all_rankings"),
      actions: {
        call: true
      }
    },
    {
      resource: Index("unique_User_username"),
      actions: {
        unrestricted_read: false,
        read: true
      }
    },
    {
      resource: Collection("User"),
      actions: {
        read: true,
        write: false,
        create: false,
        delete: false,
        history_read: false,
        history_write: false,
        unrestricted_read: false
      }
    },
    {
      resource: Index("unique_WholeUser_username"),
      actions: {
        unrestricted_read: false,
        read: true
      }
    }
  ],
  membership: []
}

In functions:

{
  name: "login_user",
  role: Role("bootstrap"),
  body: Query(
    Lambda(
      ["username", "password"],
      Let(
        {
          match: Match(Index("unique_WholeUser_username"), Var("username")),
          user: If(Exists(Var("match")), Get(Var("match")), "false"),
          login: Login(Select("ref", Var("user")), {
            password: Var("password")
          })
        },
        { token: Select("secret", Var("login")), logginUser: Var("user") }
      )
    )
  )
}

Why is the returned data ‘null’ and not ‘Test 1’? thanks again for all your help which has enabled me to get this far …

@FreeRoss I would avoid using @embedded on every type. Fauna is opinionated on how is handles graphql requests for performance reasons. These opinions can be a little disorienting if you’re coming from the purely graphql world but they are well intentioned and have many benefits. You just need to take these, for lack of a better term, “Schema transformations”, into account when designing your database and queries. When you use @embedded types in conjunction with Fauna’s autogenerated types you can compose very complex queries that are cheap to run by default.

On your login function it looks like most of it is working as expected since you’re returning a secret successfully. So that means the issue is most likely after Login(). In the Login() function you’re selecting “ref” off the user variable and that is successfully returning a secret. This leads me to believe that in your return object you should be selecting “data”. See below. I have not tested this.

{
  name: "login_user",
  role: Role("bootstrap"),
  body: Query(
    Lambda(
      ["username", "password"],
      Let(
        {
          match: Match(Index("unique_WholeUser_username"), Var("username")),
          user: If(Exists(Var("match")), Get(Var("match")), "false"),
          login: Login(Select("ref", Var("user")), {
            password: Var("password")
          })
        },
        { token: Select("secret", Var("login")), logginUser: Select("data", Var("user")) }
      )
    )
  )
}

Personally, I was trying to avoid exposing any data privileges or functions to the public other than Login() and so I ended up having a separate query that runs after login called getMe. It takes no input other than the token I just received from the login function and set as a cookie to recall to my headers in my Apollo Client. It looks like this.

Query(Lambda("_", If(HasIdentity(), Get(Identity()), null)))

I run this before I load any of my application and on Login() I force a page refresh so that this can run. If it returns null then the user is forced to the login page, otherwise the user object is returned and set on the client using the React Context API. This lets me use the same query to check if a user has been authenticated both on Login() or if they navigate away from my site and come back later.

One last thing. I do not think you need @embedded on your User object. As I said before Fauna does a transform on your schema on upload but I think if you’re using the @resolver directive on your Mutation/Query then Fauna will leave your return type alone. In my opinion that is the biggest learning curve coming from plain graphql to Fauna.

  • Sometimes Fauna automatically changes your return types. Example: Writing a Query with the same name as an index and return type MyType will be rewritten to having the return type become the automatically generated type MyTypePage
  • Sometimes Fauna leaves your return types alone. Example: When using Fauna’s @resolver directive
  • Sometimes you want to force Fauna to autogenerate and change your return types for performance and cost reasons. Example: When using Fauna’s @resolver directive AND passing the paginated=true argument and using return type MyType then it will be rewritten to the automatically generated type MyTypePage
  • Sometimes you want to tell Fauna to leave your type alone. Example: Using the @embedded directive is documented as a way to tell Fauna that this type will be embedded into another type and so it will not perform transforms on it or generate any Collections with the same name. I use this directive anytime I want to tell Fauna to leave this type alone and I will handle the logic myself.

Sorry for the long response! I hope there is a solution in here somewhere for you haha :upside_down_face:

1 Like

@yellowspaceboots
Thank you very much for the comprehensive answer. I may not fully appreciate every nuance at this point, but can refer back to it later on.
For now it answers the question originally asked … I get the User type fields in the response as well as the token (and I have successfully removed @embedded from the original types).
I also want to retrieve the Ref id of the User document, so that I can refer to it in the client app and for subsequent queries/updates. I realize this should be trivial. I therefore have some feedback for Fauna (if this is being followed) as well as another question which I apologize for troubling you to answer.
Feedback for Fauna - whilst the docs are comprehensive, I don’t find them helpful in resolving the simplest issues like this one. I used the cheat sheet to lookup ‘Ref’ and it appears I need a Ref id to complete the operation. I appreciate this is because I am misunderstanding perhaps a basic concept. Nonetheless, the docs are not currently helping me to overcome it independently. I also don’t know how to update this post with the label ‘Feedback’ once it has been started.
By setting out my attempt to retrieve the Ref below it may be possible to discern what it is that’s preventing me from applying the information in the docs and in the PG error message to my definitions.
The error:

{
  "data": {
    "loginUser": {
      "token": "fnED7KDHaMACBwPk7MnXsAYMu7KXjgCY-paYMSOirXl0hnsNdIc",
      "logginUser": {
        "username": "Test 1",
        "description": "Fast"
      },
      "id": null
    }
  },
  "errors": [
    {
      "message": "Can't convert 'ref(id = \"280684638795465228\", class = ref(id = \"User\", class = ref(id = \"classes\")))' to String",
      "path": [
        "loginUser",
        "id"
      ],
      "locations": [
        {
          "line": 8,
          "column": 5
        }
      ]
    }
  ]
}

the index:

{
  name: "unique_WholeUser_username",
  unique: true,
  serialized: true,
  source: "User",
  terms: [
    {
      field: ["data", "username"]
    }
  ]
}

the function:

{
  name: "login_user",
  role: Role("bootstrap"),
  body: Query(
    Lambda(
      ["username", "password"],
      Let(
        {
          match: Match(Index("unique_WholeUser_username"), Var("username")),
          user: If(Exists(Var("match")), Get(Var("match")), "false"),
          login: Login(Select("ref", Var("user")), {
            password: Var("password")
          })
        },
        {
          token: Select("secret", Var("login")),
          logginUser: Select("data", Var("user")),
          id: Select("ref", Var("user"))
        }
      )
    )
  )
}

the relevant schema entries:

  loginUser(username: String!, password: String!): loginResult! @resolver(name: "login_user")
}

type loginResult @embedded
{
  token : String
  logginUser : User
  id : String
}

How do I retrieve/convert the Ref id?
Thanks again for the effective answer to my issues and all the background info which I will assimilate into my efforts going forward …

A field called _id is automatically available to you on the User type. I believe this is true for all types that are tied directly to Collections. This is really why you need to use the Schema file and the graphql docs in the graphql playground in Fauna’s dashboard instead of the file you created yourself. In fact, if you try to add the _id to your type defs and then upload you will get an error.

I believe all you need to do is add the field _id to the return in your query.

query {
  loginUser(username: "Test 1", password: "hello"){
    token
    logginUser {
      _id
      username
      description
    }
  }
}
1 Like

@yellowspaceboots
Thank you very much. You have solved the issue and given me a lot of useful pointers in the process.
I have had to switch between the various threads a number of times seeing the same problems arise, but in slightly different contexts. The thorough nature of your responses enabled me to put the missing pieces together. :pray:
I have noted your comment regarding pagination, although I will not actually implement that just yet.
For anyone else who may follow this issue, resolving the _id problem involved deleting the database and starting from scratch as I had somehow ‘broken’ the automatic _id in the schema and I could see no other way to bring that back.
I will be more cautious about potential naming collisions in future and I am going to avoid updating the schema from within VSCode as Fauna advised me against that and, given the issues mentioned by yellowspaceboots, it may, perhaps, have been the source of problems.
Currently the ‘Instance data’ problem has gone on upload via the UI. Ignoring it and uploading via VSCode perhaps simply exacerbates schema related problems.

1 Like