Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
Dave Handy
@dave-handy
hi @yanns ! I see you are also working on migrating to github actions... have you tried publishing a new signed version yet? I'm having an issue with the action not seeing PGP_SECRET
Yann Simon
@yanns
@SagarJa07481451_twitter no I don't use an external schema file. Sorry I might have misunderstood you. I thought you want to avoid using the macro derivation to generate the GraphQL schema.
@dave-handy the publishing with github actions is disabled for now: sangria-graphql/macro-visit#29
This would requite much more work.
5 replies
Yann Simon
@yanns
https://github.com/sangria-graphql/sangria-slowlog is now using github actions & auto-formatting
Yann Simon
@yanns
same now for https://github.com/sangria-graphql/sangria-msgpack. If you want to help, you can have a look at the changed that were necessary, and apply them to other repositories.
Yann Simon
@yanns
Binh Nguyen
@ngbinh
:clap:
Philipp Zerelles
@pz_mxu_gitlab
Hello, Can someone maybe help me? I create a schema from AST and in the AST I use a custom scalar "DateTime". Now I also created a ScalarType for that type and use ScalarResolver to assign it to the scalar defined in the AST. But for outputs with the custom type, I get an exception that String cannot be cast to LocalDateTime and my custom "coerceOutput" is not called at all. What am I missing?
Yann Simon
@yanns
An example of a ScalarType we are using:
  implicit val DateTimeType = ScalarType[DateTime](
    "DateTime",
    description = Some(
      "DateTime is a scalar value that represents an [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) formatted date and time."),
    coerceOutput = (date, _) => ISODateTimeFormat.dateTime().print(date),
    coerceUserInput = {
      case s: String =>
        parseDateTime(s) match {
          case scala.util.Success(date) => Right(date)
          case scala.util.Failure(_) => Left(DateCoercionViolation)
        }
      case _ => Left(DateCoercionViolation)
    },
    coerceInput = {
      case ast.StringValue(s, _, _, _, _) =>
        parseDateTime(s) match {
          case scala.util.Success(date) => Right(date)
          case scala.util.Failure(_) => Left(DateCoercionViolation)
        }
      case _ => Left(DateCoercionViolation)
    }
  )
abhishekvaidprophecy
@abhishekvaidprophecy

I'm new to Sangria. I need to create an Argument which is of custom object type.

val authorizationContextArg: Argument[DefaultInput] = Argument(
    "authorizationContext",
    InputObjectType(
      "AuthorizationContext",
      "Define the Auhorization Context of the Search",
      fields = List(
        InputField("authEntityKind", StringType, description = "Auth Entity Kind"),
        InputField("authEntityId",   StringType, description = "Auth Entity Id")
      )
    )
  )

Now, in my resolver, I'm accessing this argument like this ?

val arg             = ctx.arg(authorizationContextArg)

My query is as to how do I now access both the authEntityKind and authEntityId from arg ?

macchiatow
@macchiatow
does anyone know why resolve function of DynamicDirectiveResolver never called where directive define on Interface or Type (this is not a case with field), so far I use middleware as workaround.
TrasheRacer
@TrasheRacer

Hi everyone, first time poster but long time Sangria user,

Our team has identified what appears to be a bug in sangria.Schema.

Looks like there is some side-effect causing Schema() to throw an NPE when it calls collectTypes(). We haven't yet figured out where it's coming from - possibly something to do with lazy val usage. Any advice on fixing this would be appreciated!

I can provide a unit test which reproduces the bug on request.
Cheers! Jake

Nick Hudkins
@nickhudkins
Hey @TrasheRacer yes please, a simple test case would be wonderful!
TrasheRacer
@TrasheRacer
Here's the test case:
import org.scalatest.funsuite.AnyFunSuite
import sangria.marshalling.{CoercedScalaResultMarshaller, FromInput, ResultMarshaller}
import sangria.schema.{Argument, Field, InputField, InputObjectType, LongType, ObjectType, Schema, fields}

/**
  * Below are simplified versions of the classes, objects and traits used in the real implementation of our schema,
  * to reproduce the bug with minimal complexity.
  */
object SchemaInitTest {
  case class ProjectData(name: String = "empty_project", activities: Seq[ActivityRef] = Seq.empty)
  case class Project(projectData: ProjectData)
  case class QueryableProject(project: Project)
  object QueryableProject {
    val empty: QueryableProject = QueryableProject(Project(ProjectData()))
  }
  trait ProjectsContext[T <: ProjectsContext[T]]
  trait POWContext extends ProjectsContext[POWContext]
  case class ProjectValue[T](project: QueryableProject, value: T)
  object ProjectValue {
    def ofActivityRef(queryableProject: QueryableProject, activityRef: ActivityRef): ProjectValue[ActivityRef] =
      ProjectValue(queryableProject, activityRef)
  }

  case class ActivityRef(id: Long)
  object ActivityRef {
    implicit val fromInput: FromInput[ActivityRef] = new FromInput[ActivityRef] {
      override val marshaller: ResultMarshaller = CoercedScalaResultMarshaller.default
      override def fromResult(node: marshaller.Node): ActivityRef = ActivityRef(3456L)
    }

    // TODO: FIND OUT WHY OUR BUG DISAPPEARS WHEN THIS IS CHANGED INTO A LAZY VAL
    val arg: Argument[ActivityRef] = Argument("activityRef", inputType)
  }

  val inputType: InputObjectType[ActivityRef] =
    InputObjectType[ActivityRef](
      "ActivityRefInput",
      fields = List(
        InputField("id", LongType)
      )
    )

  // TODO: FIND OUT WHY NPE DISAPPEARS WHEN THIS IS MADE LAZY
  val activityRefArgs = ActivityRef.arg :: Nil
}

class SchemaInitTest extends AnyFunSuite {

  import SchemaInitTest._

  test("ObjectType() and Schema() should not have any kind of weird side effects when sharing an argument") {
    // Do something which (apparently) mutates global state somewhere in sangria
    ObjectType(
      "Activity",
      fields[Unit, QueryableProject](
        Field(
          "activityRef",
          LongType,
          arguments = ActivityRef.arg :: Nil,
          resolve = _ => 123L // The resolve function is irrelevant to this test
        )
      )
    )

    val query =       ObjectType(
      name = "Query",
      fields = fields[POWContext, Unit](
        Field(
          name = "project",
          fieldType = ObjectType[Unit, QueryableProject](
            name = "Project",
            fields = fields[Unit, QueryableProject](
              Field(
                "activityRef",
                LongType,
                arguments = activityRefArgs,
                resolve = _ => 123L // The resolve function is irrelevant to this test
              )
            )
          ),
          resolve = _ => QueryableProject.empty // The resolve function is irrelevant to this test
        )
      )
    )


    // Create a Schema which **shouldn't** access any global mutable state
    Schema(query)

    // No exception should be thrown...
    assert(true)
  }
}
I would have created a Scalafiddle, but it looks like they don't support Sangria as a library...
TrasheRacer
@TrasheRacer
I went ahead and put this test in my fork of the sangria-playground repo
https://github.com/TrasheRacer/sangria-playground/tree/bug-in-schema-init
image.png
TrasheRacer
@TrasheRacer


java.lang.NullPointerException was thrown.
java.lang.NullPointerException
    at sangria.schema.Schema.$anonfun$types$4(Schema.scala:848)
    at scala.collection.LinearSeqOps.foldLeft(LinearSeq.scala:168)
    at scala.collection.LinearSeqOps.foldLeft$(LinearSeq.scala:164)
    at scala.collection.immutable.List.foldLeft(List.scala:79)
    at sangria.schema.Schema.$anonfun$types$3(Schema.scala:847)
    at scala.collection.IterableOnceOps.foldLeft(IterableOnce.scala:638)
    at scala.collection.IterableOnceOps.foldLeft$(IterableOnce.scala:634)
    at scala.collection.AbstractIterable.foldLeft(Iterable.scala:920)
    at sangria.schema.Schema.collectTypes$1(Schema.scala:845)
    at sangria.schema.Schema.$anonfun$types$3(Schema.scala:847)
    at scala.collection.IterableOnceOps.foldLeft(IterableOnce.scala:638)
    at scala.collection.IterableOnceOps.foldLeft$(IterableOnce.scala:634)
    at scala.collection.AbstractIterable.foldLeft(Iterable.scala:920)
    at sangria.schema.Schema.collectTypes$1(Schema.scala:845)
    at sangria.schema.Schema.types$lzycompute(Schema.scala:873)
    at sangria.schema.Schema.types(Schema.scala:787)
    at sangria.schema.Schema.inputTypes$lzycompute(Schema.scala:885)
    at sangria.schema.Schema.inputTypes(Schema.scala:885)
    at sangria.schema.DefaultValuesValidationRule$.validate(SchemaValidationRule.scala:61)
    at sangria.schema.SchemaValidationRule$.$anonfun$validate$1(SchemaValidationRule.scala:37)
    at scala.collection.immutable.List.flatMap(List.scala:293)
    at sangria.schema.SchemaValidationRule$.validate(SchemaValidationRule.scala:37)
    at sangria.schema.SchemaValidationRule$.validateWithException(SchemaValidationRule.scala:40)
    at sangria.schema.Schema.<init>(Schema.scala:939)
    at SchemaSpec.$anonfun$new$4(SchemaSpec.scala:162)
    at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
    at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
    at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
    at org.scalatest.Transformer.apply(Transformer.scala:22)
    at org.scalatest.Transformer.apply(Transformer.scala:20)
    at org.scalatest.wordspec.AnyWordSpecLike$$anon$3.apply(AnyWordSpecLike.scala:1076)
    at org.scalatest.TestSuite.withFixture(TestSuite.scala:196)
    at org.scalatest.TestSuite.withFixture$(TestSuite.scala:195)
    at org.scalatest.wordspec.AnyWordSpec.withFixture(AnyWordSpec.scala:1879)
    at org.scalatest.wordspec.AnyWordSpecLike.invokeWithFixture$1(AnyWordSpecLike.scala:1074)
    at org.scalatest.wordspec.AnyWordSpecLike.$anonfun$runTest$1(AnyWordSpecLike.scala:1086)
    at org.scalatest.SuperEngine.runTestImpl(Engine.scala:306)
    at org.scalatest.wordspec.AnyWordSpecLike.runTest(AnyWordSpecLike.scala:1086)
    at org.scalatest.wordspec.AnyWordSpecLike.runTest$(AnyWordSpecLike.scala:1068)
    at org.scalatest.wordspec.AnyWordSpec.runTest(AnyWordSpec.scala:1879)
    at org.scalatest.wordspec.AnyWordSpecLike.$anonfun$runTests$1(AnyWordSpecLike.scala:1145)
    at org.scalatest.SuperEngine.$anonfun$runTestsInBranch$1(Engine.scala:413)
    at scala.collection.immutable.List.foreach(List.scala:333)
    at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:401)
    at org.scalatest.SuperEngine.runTestsInBranch(Engine.scala:390)
    at org.scalatest.SuperEngine.$anonfun$runTestsInBranch$1(Engine.scala:427)
    at scala.collection.immutable.List.foreach(List.scala:333)
    at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:401)
    at org.scalatest.SuperEngine.runTestsInBranch(Engine.scala:396)
    at org.scalatest.SuperEngine.runTestsImpl(Engine.scala:475)
    at org.scalatest.wordspec.AnyWordSpecLike.runTests(AnyWordSpecLike.scala:1145)
    at org.scalatest.wordspec.AnyWordSpecLike.runTests$(AnyWordSpecLike.scala:1144)
    at org.scalatest.wordspec.AnyWordSpec.runTests(AnyWordSpec.scala:1879)
    at org.scalatest.Suite.run(Suite.scala:1112)
    at org.scalatest.Suite.run$(Suite.scala:1094)
    at org.scalatest.wordspec.AnyWordSpec.org$scalatest$wordspec$AnyWordSpecLike$$super$run(AnyWordSpec.scala:1879)
    at org.scalatest.wordspec.AnyWordSpecLike.$anonfun$run$1(AnyWordSpecLike.scala:1190)
    at org.scalatest.SuperEngine.runImpl(Engine.scala:535)
    at org.scalatest.wordspec.AnyWordSpecLi
Nick Hudkins
@nickhudkins
@TrasheRacer can you give v2.0.0 of sangria a try? I can take a look at this in a bit, but I just want to quick confirm that it wasn't recently introduced
Nick Hudkins
@nickhudkins
ah nevermind, @TrasheRacer looks like you've got a Scala issue not a Sangria bug... let me quick show you what's going on
// Start writing your ScalaFiddle code here
object SchemaInitTest {
  object ActivityRef {
    val arg = someOtherThing
  }
  // ActivityRef.arg references something that has yet to be initialized.
  println(ActivityRef.arg)

  // Go ahead and make this eager (not lazy), and you'll get `null`
  lazy val someOtherThing = "Foo"
}

val _ = SchemaInitTest
If you plan to reference vals that are declared after your usage, that val must be lazy, or it needs to have been initialized by the time you reference it
You can fix this 1 of two ways, either make the val lazy... or move the declaration like so:
// Start writing your ScalaFiddle code here
object SchemaInitTest {
  val someOtherThing = "Foo"
  object ActivityRef {
    val arg = someOtherThing
  }
  println(ActivityRef.arg)

}

val _ = SchemaInitTest
TrasheRacer
@TrasheRacer

Thanks for the quick reply!

I believe the issue you just pointed out with the reference to a val declared after it's usage was just a relic writing this test; I first copy-pasted a lot of our schema implementation code into from multiple files into just this one file, then stripping out some of the other stuff. The order of initialisation wasn't an issue when our implementation was all split across different objects.

I've fixed the order of val creation in my test code - I don't think there are any more forward references, and the NPE from Schema() still occurs:
import org.scalatest.funsuite.AnyFunSuite
import sangria.marshalling.{CoercedScalaResultMarshaller, FromInput, ResultMarshaller}
import sangria.schema.{Argument, Field, InputField, InputObjectType, LongType, ObjectType, Schema, fields}

/**
  * Below are simplified versions of the classes, objects and traits used in the real implementation of our schema,
  * to reproduce the bug with minimal complexity.
  */
object SchemaInitTest {
  case class ProjectData(name: String = "empty_project", activities: Seq[ActivityRef] = Seq.empty)
  case class Project(projectData: ProjectData)
  case class QueryableProject(project: Project)
  object QueryableProject {
    val empty: QueryableProject = QueryableProject(Project(ProjectData()))
  }
  trait ProjectsContext[T <: ProjectsContext[T]]
  trait POWContext extends ProjectsContext[POWContext]
  case class ProjectValue[T](project: QueryableProject, value: T)
  object ProjectValue {
    def ofActivityRef(queryableProject: QueryableProject, activityRef: ActivityRef): ProjectValue[ActivityRef] =
      ProjectValue(queryableProject, activityRef)
  }

  case class ActivityRef(id: Long)

  val inputType: InputObjectType[ActivityRef] =
    InputObjectType[ActivityRef](
      "ActivityRefInput",
      fields = List(
        InputField("id", LongType)
      )
    )

  object ActivityRef {
    implicit val fromInput: FromInput[ActivityRef] = new FromInput[ActivityRef] {
      override val marshaller: ResultMarshaller = CoercedScalaResultMarshaller.default
      override def fromResult(node: marshaller.Node): ActivityRef = ActivityRef(3456L)
    }
    val arg: Argument[ActivityRef] = Argument("activityRef", inputType)
  }

  // TODO: FIND OUT WHY NPE DISAPPEARS WHEN THIS IS MADE LAZY
  val activityRefArgs = ActivityRef.arg :: Nil
}

class SchemaInitTest extends AnyFunSuite {

  import SchemaInitTest._

  test("ObjectType() and Schema() should not have any kind of weird side effects when sharing an argument") {
    // Do something which (apparently) mutates global state somewhere in sangria
    ObjectType(
      "Activity",
      fields[Unit, QueryableProject](
        Field(
          "activityRef",
          LongType,
          arguments = ActivityRef.arg :: Nil,
          resolve = _ => 123L // The resolve function is irrelevant to this test
        )
      )
    )

    val query =       ObjectType(
      name = "Query",
      fields = fields[POWContext, Unit](
        Field(
          name = "project",
          fieldType = ObjectType[Unit, QueryableProject](
            name = "Project",
            fields = fields[Unit, QueryableProject](
              Field(
                "activityRef",
                LongType,
                arguments = activityRefArgs,
                resolve = _ => 123L // The resolve function is irrelevant to this test
              )
            )
          ),
          resolve = _ => QueryableProject.empty // The resolve function is irrelevant to this test
        )
      )
    )


    // Create a Schema which **shouldn't** access any global mutable state
    Schema(query)

    // No exception should be thrown...
    assert(true)
  }
}
Nick Hudkins
@nickhudkins
Just cloned your repo and taking a look now
TrasheRacer
@TrasheRacer
Cheers, appreciated
Nick Hudkins
@nickhudkins
When I unwrap the ActivityRef object, it behaves. I think this is still some strange Scala initialization issue :\
  object ActivityRef {
    implicit val fromInput: FromInput[ActivityRef] =
      new FromInput[ActivityRef] {
        override val marshaller: ResultMarshaller =
          CoercedScalaResultMarshaller.default
        override def fromResult(node: marshaller.Node): ActivityRef =
          ActivityRef(3456L)
      }
    val activityRefArg: Argument[ActivityRef] =
      Argument("activityRef", inputType)
  }

  object Args {
    import ActivityRef._
    val activityRefArgs = activityRefArg :: Nil
  }
    // Do something which (apparently) mutates global state somewhere in sangria
    ObjectType(
      "Activity",
      fields[Unit, QueryableProject](
        Field(
          "activityRef",
          LongType,
          arguments = ActivityRef.activityRefArg :: Nil,
          resolve = _ => 123L // The resolve function is irrelevant to this test
        )
      )
    )

    val query = ObjectType(
      name = "Query",
      fields = fields[POWContext, Unit](
        Field(
          name = "project",
          fieldType = ObjectType[Unit, QueryableProject](
            name = "Project",
            fields = fields[Unit, QueryableProject](
              Field(
                "activityRef",
                LongType,
                arguments = Args.activityRefArgs,
                resolve =
                  _ => 123L // The resolve function is irrelevant to this test
              )
            )
          ),
          resolve =
            _ => QueryableProject.empty // The resolve function is irrelevant to this test
        )
      )
    )
class SchemaInitTest extends AnyFunSuite {

  import SchemaInitTest._

  test(
    "ObjectType() and Schema() should not have any kind of weird side effects when sharing an argument"
  ) {
    // Do something which (apparently) mutates global state somewhere in sangria

    println(ActivityRef.arg :: Nil) // List(Good stuff!)
    println(activityRefArgs) // List(null)
    assert(true)
  }
}
Nick Hudkins
@nickhudkins
for context ^^ the issue is that activityRefArgs is List(null) , which means Sangria ends up throwing a null pointer exception. I suspect flattening out some of your objects might help this
Nick Hudkins
@nickhudkins
Does that help @TrasheRacer ?
TrasheRacer
@TrasheRacer
Yes, that helps enormously! Thanks :pray:
I'll keep looking into the bug - if it turns out to be a Sangria bug, I'll open an issue
TrasheRacer
@TrasheRacer

Regarding the above mentioned bug involving Argument()

I'm think that this is a Sangria-related issue, as opposed to simply a Scala issue.
When I get rid of all the Sangria code (instead just constructing a list containing some case class), the null disappears.

import org.scalatest.funsuite.AnyFunSuite
import sangria.schema.{Argument, EnumType, EnumValue, OptionInputType}

object SchemaInitTest {
  sealed trait Episode
  object Episode {
    final case object EPISODE_1 extends Episode
  }

  case class Test(string: String)

  val EpisodeEnum = EnumType(
    "Episode",
    Some("One of the films in the Star Wars Trilogy"),
    List(EnumValue("NEWHOPE", value = Episode.EPISODE_1, description = Some("Released in 1977.")))
  )

  object EpisodeArgs {
//    val arg = Test("test") // test passes when we don't use Sangria code at all!
    val arg = Argument("activityRef", OptionInputType(EpisodeEnum)) // but this fails!
  }

  val episodeArgs = EpisodeArgs.arg :: Nil
}

class SchemaInitTest extends AnyFunSuite {

  import SchemaInitTest._

  test("A list of args should be the same, regardless of where it is initialised") {
//    assert(episodeArgs == EpisodeArgs.arg :: Nil) // this passes for some reason!
    assert(EpisodeArgs.arg :: Nil == episodeArgs) // but this fails!
  }
}

Another thing I noticed is that changing the order we compare for equality in the assert also makes the problem go away.

macchiatow
@macchiatow
please please help me to find out the way to register subscription via FieldResolver. I build scheme from ast from text file by use of ResolverBasedAstSchemaBuilder. Yet the only way to make a subscription is via Field::sub which creates a field, where I'm trying to create a resolver
  private def builder: ResolverBasedAstSchemaBuilder[UserContext] =
    AstSchemaBuilder.resolverBased[UserContext](
      riFieldResolver.resolvers :+ FieldResolver.defaultInput[UserContext, JsValue]: _*
    )

  private val schemaString = Source.fromResource("schema.graphql").mkString
  private val schemaAst = QueryParser.parse(schemaString).get

  val schema: Schema[UserContext, Any] = Schema.buildFromAst(schemaAst, builder.validateSchemaWithException(schemaAst))
macchiatow
@macchiatow
  private val riThreadResolver = FieldResolver[UserContext] {
    case (TypeName("Subscription"), FieldName("fi_thread")) =>
      Field.subs("allEvents", null, resolve = { _: Context[UserContext, _] =>
        Source(1 to 10)
          .map { _ =>
            s"""{
                  "data": {
                    "fi_thread": "${LocalTime.now().toString}",
                    "fi_thread2": "${LocalTime.now().toString}"

                  }
                  }"""
          }
          .map(Action(_))
      }).resolve
  }
macchiatow
@macchiatow
        private[sangria] case class SubscriptionValue[Ctx, Val, S[_]](source: Val, stream: SubscriptionStream[S]) extends LeafAction[Ctx, Val] {
        override def map[NewVal](fn: Val => NewVal)(implicit ec: ExecutionContext): SubscriptionValue[Ctx, NewVal, S] =
        throw new IllegalStateException("`map` is not supported subscription actions. Action is only intended for internal use.")
        }
    this is probably useful implicit but it's private
Nick Hudkins
@nickhudkins
Hey @TrasheRacer if you replace EnumType with StringType, this also passes... so, down the rabbit hole I go. I suspect this has something to do with needing to render EnumValues out, but not sure yet :)
If I am SUPERRRR honest, I can't stand all of these nested objects as is, and simply taking these objects and flattening them out also behaves as expected. I am however, fascinated by the behavior. I still am convinced this is a Scala semantics thing that is way over my head. But I am happy to keep seeing if I can find what is happening.
Nick Hudkins
@nickhudkins
Current hunch is: https://github.com/sangria-graphql/sangria/blob/master/modules/core/src/main/scala/sangria/schema/Schema.scala#L890-L893, those lazy vals are used in the coercions. Running some tests as we speak
NOPE! Alright and with that I am tapping out on this one. Please re-organize all of your code :P
Nick Hudkins
@nickhudkins
Ok cool it happens with any Schema type that extends with NullableType, any non simple scalars do the trick :) I have this funny feeling that it's nearly impossible to get into this state in a RealLifeApplication™ but, maybe you already said you have this in a real app?
Nick Hudkins
@nickhudkins
Regarding the switching left / right hand comparison, the reason that works is that it forces coercion. I have never looked deeply into what all goes on behind the scenes in sangria, so maybe it's time. As I looked deeper, it looks like the nullability come from a A <: Null type constraint, and that's deep in Tree (part of the Ast code) That said... I am currently fully unqualified to fix any of this, but I would encourage you to fix it in user land at the moment (a small re-organization), but I'll see if I can get some failing test cases added and maybe someone could look deeper
danielbussone
@danielbussone
Can anyone point me to an example of implementing a filter on a subscription? I would like a user to be able to subscribe to project update events using something like projectUpdated(projectId: "someProjectId") where only events with an id matching the subscribed projectId are returned to the subscriber.
Yann Simon
@yanns
@danielbussone about subscription (https://sangria-graphql.github.io/learn/#stream-based-subscriptions): in the resolve function, you can access the arguments to push only desired events I think.