3

I am stuck trying to define what seems to me like a very basic mutation. I am new to all of Scala, GraphQL, Akka HTTP, and not a native speaker, so forgive me if anything below is nonsense! Sorry for the lengthy post, I did try to keep it short with minimal examples but I feel that complete context was important.

Context

I have the following code that defines a Transaction case class, a fake DB for all transactions, and a schema to query and create transactions:

case class Transaction(id: Int, description: String, amount: BigDecimal)

object TransactionDB {
  var transactions: List[Transaction] = Nil
}

class TransactionDB {
  def transaction(id: Int): Option[Transaction] =
    TransactionDB.transactions.find(_.id == id)
  def createTransaction(transaction: Transaction) = {
    TransactionDB.transactions = transaction :: TransactionDB.transactions
    transaction
  }
}

import sangria.macros.derive._
import sangria.marshalling.sprayJson._
import sangria.schema._
import spray.json._
import DefaultJsonProtocol._

object TransactionSchema {
  val Id = Argument("id", IntType)
  val TransactionType = deriveObjectType[Unit, Transaction]()
  val QueryType = ObjectType("Query", fields[TransactionDB, Unit](
    Field("transaction", OptionType(TransactionType),
      arguments = Id :: Nil,
      resolve = c ⇒ c.ctx.transaction(c.arg(Id))
    )
  ))

  implicit val transactionFormat = jsonFormat3(Transaction)
  val TransactionInputType = deriveInputObjectType[Transaction](
    InputObjectTypeName("TransactionInput")
  )

  val TransactionArg = Argument("transaction", TransactionInputType)
  val MutationType = ObjectType("Mutation", fields[TransactionDB, Unit](
    Field("createTransaction", TransactionType,
      arguments = TransactionArg :: Nil,
      resolve = c ⇒ c.ctx.createTransaction(c.arg(TransactionArg))
    )
  ))

  val schema = Schema(QueryType, Some(MutationType))
}

The resulting GraphQL schema is as follows:

type Mutation {
  createTransaction(transaction: TransactionInput!): Transaction!
}

type Query {
  transaction(id: Int!): Transaction
}

type Transaction {
  id: Int!
  description: String!
  amount: BigDecimal!
}

input TransactionInput {
  id: Int!
  description: String!
  amount: BigDecimal!
}

I can then create a transaction and query it with the following queries:

mutation {
  createTransaction(transaction: {
    id: 1
    description: "Electricity bill"
    amount: 100
  }) {
    id
  }
}

query {
  transaction(id: 1) {
    id
    description
    amount
  }
}

Problem

However, id should not be part of the mutation. Ideally, I'd get:

input TransactionInput {
  description: String!
  amount: BigDecimal!
}

type Mutation {
  createTransaction(input: TransactionInput): Transaction
  updateTransaction(id: Int!, input: TransactionInput): Transaction # For later
}

In the graphql-js documentation, this is done using something along the lines of:

createTransaction({description, amount}) {
  const id = generateId();
  transactionRepo[id] = new Transaction(id, description, amount);
  return transaction[id];
},
updateTransaction(id, {description, amount}) { // Assuming id exists
  transactionRepo[id] = new Transaction(id, description, amount);
  return transactionRepo[id];
},

Finding a solution to this in Scala / Sangria is where I'm stuck.

Attempted solutions

Attempt 1: I was thinking of something like this:

def createTransaction(description: String, amount: BigDecimal) = {
  val r = scala.util.Random // Or leave this to the DB layer
  val transaction = Transaction(r.nextInt(10000), description, amount)
  TransactionDB.transactions = transaction :: TransactionDB.transactions
  transaction
}

But this would not work as createTransaction is given a TransactionArg that maps to a Transaction.

Attempt 2: I also tried to switch to jsonFormat2 because of the removed id, but this triggers a type mismatch at compile time because Transaction has 3 attributes.

Attempt 3: I tried updating the derived InputObjectType to exclude the id using:

val TransactionInputType = deriveInputObjectType[Transaction](
  InputObjectTypeName("TransactionInput"),
  ExcludeInputFields("id")
)

This compiles, but when trying to run the mutation given above results with:

  • Argument 'transaction' has invalid value: Object is missing required member 'id' when omitting id from the createTransaction(transaction: {...}) field.
  • Argument 'transaction' expected type 'TransactionInput!' but got: {id: 42, description: \"Electricity bill\", amount: 100}. Reason: Unknown field 'id' is not defined in the input type 'TransactionInput'. when adding it back.

What am I missing? It seems straightforward with graphql-js to me but I can't find how to do that in Scala using Sangria. I read docs many times and looked extensively on Google as this seemed like a simple issue but didn't find anything similar. Thanks!

astorije
  • 2,666
  • 2
  • 27
  • 39

1 Answers1

4

It seems both createTransaction and updateTransaction return a type Transaction defined as:

type Transaction {
  id: Int!
  description: String!
  amount: BigDecimal!
}

The id: Int! states that id is required (that's what ! means) so I start by redefining it as: id: Int (no exclamation mark).

tenshi
  • 26,268
  • 8
  • 76
  • 90
toxicafunk
  • 396
  • 2
  • 7