Extensive function examples with conditional syntax

Given the following function to create a user, I want to extend it to proceed with login in case of success and return a “secret” otherwise return regular error message from FaunaDB. Is that possible by coding If conditions. I’m not able to find a complete function tutorial showing all the aspects of conditional execution, and method of calling from both GraphQL and FQL, etc. There are examples scattered in various pages but not in one place to learn function coding in depth.

Query(
  Lambda(
    ["input"],
    Create(Collection("users"), {
      data: { email: Select("email", Var("input")) },
      credentials: { password: Select("password", Var("input")) }
    })
  )
)

Thanks.

That’s fair, we are working hard to improve that. In the meantime there is an excellent series from which you can learn FQL step by step: Getting started with FQL, Fauna’s native query language - part 1, e.g. the fourth article dives into conditional logic by verifying whether the spaceship has enough fuel to warp to a planet: Getting started with FQL, Fauna’s native query language - part 4.

I also wrote a complete app with a github repository which also has quite some conditional logic: Rethinking Twitter as a Serverless App | CSS-Tricks - CSS-Tricks

You are thinking in the wrong direction imo. Everything in FaunaDB is a transaction which means that if the Create fails, the whole transaction would fail. That means you can simply continue with your login once you created the user since if the code got that far, the create succeeded. The login won’t happen if the transaction fails. If you do want to check specifics such as whether a user with that email already exists (although you could just use a unique index and let it fail) and do some logic etc you could always try to search it with an index and use the ‘Exists()’ function in combination with the If() function. That said, I’m assuming for what you are trying to do that you don’t need that.

To login after creating the user, you will need to know which user to login with, which means you need to store that user result from the create (or at least the reference) in a variable. For that, Let() is your friend.

Let(
  {
    user: Create(Collection('users'), {
      data: { email: Select('email', Var('input')) },
      credentials: { password: Select('password', Var('input')) }
    }),
    // you could add more variables here that use the previous variables e.g.
    token: Login(Select(['ref'], Var('user')), Select('password', Var('input')))
  },
  // and then return it.
  {
    token: Var('token'),
    user: Var('user')
  }
)

You could also just create the token since you already know that the password is correct, you just created the user.

Let(
  {
    user: Create(Collection('users'), {
      data: { email: Select('email', Var('input')) },
      credentials: { password: Select('password', Var('input')) }
    }),
    // you could add more variables here that use the previous variables e.g.
    token: Create(Tokens(), { instance: Select(['ref'], Var('user')) })
  },
  // and then return it.
  {
    token: Var('token'),
    user: Var('user')
  }
)

Note that I didn’t test the code, there might be syntax errors.

I do think you already found this out right? We definitely need more guidance there. In your case. You will have to be careful that the result of your query closely matches the result that GraphQL expects which is probably the hardest part.

There are few basic examples here but we definitely need something more advanced:

Tips:

  • make sure to define your schema to return the types you intend to return from your UDF.
  • make sure not to return FQL formats such as ‘Ref’, take the id out of there.
  • when in doubt, look at another GraphQL result carefully and try to see the difference with the FQL result (you could just test by calling your UDF via the dashboard console).

This is super helpful. I’m impressed by what all is possible. I will try it. Many thanks.

Update: Solved it. Used the following as Let expr.
Select([“secret”], Var(“token”))

I tried your first suggestion and it works great. But I want the output to be quite similar to output of login so that my parsing code does not change at client end. Here is the output of login call:

{
  "data": {
    "login_User": "fnEDzsW8hGA....."
  }
}

How can I get an output similar where the function name may come in place of login_User. What I tried was changing the return type of mutation to String

   create_User2(input: ActionUserInput): String!

and in the Let output make it return only Var(‘token’) with any braces around it.

When I run it now:

mutation CreateAUser {
  create_User2(input: {
    email: "john2@doe.com"
    password: "1234"
  })
}

I get an error in playground:

Update: Solved it. Used the following as Let expr.
Select([“secret”], Var(“token”))

Please ignore rest…

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

Do you see any problem? Meanwhile, I will keep trying variations. Thanks.

Cool, but… I’m confused.
Which question is still open and which part is solved? :slight_smile:
I do think that solves the ‘Object expected, String provided’ error right?

Actually, what worked was the second solution sending expression as secret from the token object.

I couldn’t make the first solution work with whatever I put in expression. I will try it later sometime.

BTW, what permissions are needed to read the tokens? I’m asking because I intermittently get “Insufficient privileges” with the Create function using the second solution. Can’t explain it or find a pattern.

Thanks

Tokens is a collection so you can use it similarly as regular collections. E.g:

Note however that that gives that key/token/function access to create all kinds of tokens, no matter what instance they are on. Doesn’t matter for most people but in the case it does for you, you can limit that again with more fine-grained access rules (a query or lambda instead of the ‘true’ boolean).