cats.effect.IO Scala Examples

The following examples show how to use cats.effect.IO. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.
Example 1
Source File: ClientApp.scala    From fs2-chat   with MIT License 6 votes vote down vote up
package fs2chat
package client

import cats.effect.{Blocker, ExitCode, IO, IOApp}
import cats.implicits._
import com.comcast.ip4s._
import com.monovore.decline._
import fs2.io.tcp.SocketGroup

object ClientApp extends IOApp {
  private val argsParser: Command[(Username, SocketAddress[IpAddress])] =
    Command("fs2chat-client", "FS2 Chat Client") {
      (
        Opts
          .option[String]("username", "Desired username", "u")
          .map(Username.apply),
        Opts
          .option[String]("address", "Address of chat server")
          .withDefault("127.0.0.1")
          .mapValidated(p => IpAddress(p).toValidNel("Invalid IP address")),
        Opts
          .option[Int]("port", "Port of chat server")
          .withDefault(5555)
          .mapValidated(p => Port(p).toValidNel("Invalid port number"))
      ).mapN {
        case (desiredUsername, ip, port) =>
          desiredUsername -> SocketAddress(ip, port)
      }
    }

  def run(args: List[String]): IO[ExitCode] =
    argsParser.parse(args) match {
      case Left(help) => IO(System.err.println(help)).as(ExitCode.Error)
      case Right((desiredUsername, address)) =>
        Blocker[IO]
          .use { blocker =>
            Console[IO](blocker).flatMap { console =>
              SocketGroup[IO](blocker).use { socketGroup =>
                Client
                  .start[IO](console, socketGroup, address, desiredUsername)
                  .compile
                  .drain
              }
            }
          }
          .as(ExitCode.Success)
    }
} 
Example 2
Source File: MySqlZoneChangeRepository.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.mysql.repository

import cats.effect.IO
import org.joda.time.DateTime
import org.slf4j.LoggerFactory
import scalikejdbc._
import vinyldns.core.domain.zone._
import vinyldns.core.protobuf._
import vinyldns.core.route.Monitored
import vinyldns.proto.VinylDNSProto

class MySqlZoneChangeRepository
    extends ZoneChangeRepository
    with ProtobufConversions
    with Monitored {
  private final val logger = LoggerFactory.getLogger(classOf[MySqlZoneChangeRepository])

  private final val PUT_ZONE_CHANGE =
    sql"""
      |REPLACE INTO zone_change (change_id, zone_id, data, created_timestamp)
      |  VALUES ({change_id}, {zone_id}, {data}, {created_timestamp})
      """.stripMargin

  private final val LIST_ZONES_CHANGES =
    sql"""
      |SELECT zc.data
      |  FROM zone_change zc
      |  WHERE zc.zone_id = {zoneId} AND zc.created_timestamp <= {startFrom}
      |  ORDER BY zc.created_timestamp DESC
      |  LIMIT {maxItems}
    """.stripMargin

  override def save(zoneChange: ZoneChange): IO[ZoneChange] =
    monitor("repo.ZoneChange.save") {
      IO {
        logger.info(s"Saving zone change ${zoneChange.id}")
        DB.localTx { implicit s =>
          PUT_ZONE_CHANGE
            .bindByName(
              'change_id -> zoneChange.id,
              'zone_id -> zoneChange.zoneId,
              'data -> toPB(zoneChange).toByteArray,
              'created_timestamp -> zoneChange.created.getMillis
            )
            .update()
            .apply()

          zoneChange
        }
      }
    }

  override def listZoneChanges(
      zoneId: String,
      startFrom: Option[String],
      maxItems: Int
  ): IO[ListZoneChangesResults] =
    // sorted from most recent, startFrom is an offset from the most recent change
    monitor("repo.ZoneChange.listZoneChanges") {
      IO {
        logger.info(s"Getting zone changes for zone $zoneId")
        DB.readOnly { implicit s =>
          val startValue = startFrom.getOrElse(DateTime.now().getMillis.toString)
          // maxItems gets a plus one to know if the table is exhausted so we can conditionally give a nextId
          val queryResult = LIST_ZONES_CHANGES
            .bindByName(
              'zoneId -> zoneId,
              'startFrom -> startValue,
              'maxItems -> (maxItems + 1)
            )
            .map(extractZoneChange(1))
            .list()
            .apply()
          val maxQueries = queryResult.take(maxItems)

          // nextId is Option[String] to maintains backwards compatibility
          // earlier maxItems was incremented, if the (maxItems + 1) size is not reached then pages are exhausted
          val nextId = queryResult match {
            case _ if queryResult.size <= maxItems | queryResult.isEmpty => None
            case _ => Some(queryResult.last.created.getMillis.toString)
          }

          ListZoneChangesResults(maxQueries, nextId, startFrom, maxItems)
        }
      }
    }

  private def extractZoneChange(colIndex: Int): WrappedResultSet => ZoneChange = res => {
    fromPB(VinylDNSProto.ZoneChange.parseFrom(res.bytes(colIndex)))
  }
} 
Example 3
Source File: MySqlUserChangeRepository.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.mysql.repository

import cats.effect.IO
import org.slf4j.LoggerFactory
import scalikejdbc._
import vinyldns.core.domain.membership.{UserChange, UserChangeRepository}
import vinyldns.core.protobuf.ProtobufConversions
import vinyldns.core.route.Monitored
import vinyldns.proto.VinylDNSProto

class MySqlUserChangeRepository
    extends UserChangeRepository
    with Monitored
    with ProtobufConversions {
  private final val logger = LoggerFactory.getLogger(classOf[MySqlUserChangeRepository])

  private final val PUT_USER_CHANGE =
    sql"""
         |  INSERT INTO user_change (change_id, user_id, data, created_timestamp)
         |       VALUES ({changeId}, {userId}, {data}, {createdTimestamp}) ON DUPLICATE KEY
         |       UPDATE user_id=VALUES(user_id),
         |              data=VALUES(data),
         |              created_timestamp=VALUES(created_timestamp)
       """.stripMargin

  private final val GET_USER_CHANGE_BY_ID =
    sql"""
         |  SELECT data
         |    FROM user_change
         |   WHERE change_id = ?
       """.stripMargin

  def get(changeId: String): IO[Option[UserChange]] =
    monitor("repo.UserChange.get") {
      logger.info(s"Getting user change with id: $changeId")
      IO {
        DB.readOnly { implicit s =>
          GET_USER_CHANGE_BY_ID
            .bind(changeId)
            .map(toUserChange(1))
            .first()
            .apply()
        }
      }
    }

  def save(change: UserChange): IO[UserChange] =
    monitor("repo.UserChange.save") {
      logger.info(s"Saving user change: $change")
      IO {
        DB.localTx { implicit s =>
          PUT_USER_CHANGE
            .bindByName(
              'changeId -> change.id,
              'userId -> change.madeByUserId,
              'data -> toPb(change).toByteArray,
              'createdTimestamp -> change.created.getMillis
            )
            .update()
            .apply()
        }
        change
      }
    }

  private def toUserChange(colIndex: Int): WrappedResultSet => UserChange = res => {
    fromPb(VinylDNSProto.UserChange.parseFrom(res.bytes(colIndex)))
  }
} 
Example 4
Source File: MySqlMessageQueueProvider.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.mysql.queue

import cats.effect.IO
import org.slf4j.LoggerFactory
import pureconfig._
import pureconfig.generic.auto._
import pureconfig.module.catseffect.syntax._
import scalikejdbc.{ConnectionPool, DataSourceConnectionPool}
import scalikejdbc.config.DBs
import vinyldns.core.queue.{MessageQueue, MessageQueueConfig, MessageQueueProvider}
import vinyldns.mysql.{HikariCloser, MySqlConnectionConfig, MySqlDataSourceSettings}
import vinyldns.mysql.MySqlConnector._
import cats.effect.ContextShift
import cats.effect.Blocker

class MySqlMessageQueueProvider extends MessageQueueProvider {

  private val logger = LoggerFactory.getLogger(classOf[MySqlMessageQueueProvider])

  implicit val mySqlPropertiesReader: ConfigReader[Map[String, AnyRef]] =
    MySqlConnectionConfig.mySqlPropertiesReader

  private implicit val cs: ContextShift[IO] =
    IO.contextShift(scala.concurrent.ExecutionContext.global)

  def load(config: MessageQueueConfig): IO[MessageQueue] =
    for {
      connectionSettings <- Blocker[IO].use(
        ConfigSource.fromConfig(config.settings).loadF[IO, MySqlConnectionConfig](_)
      )
      _ <- runDBMigrations(connectionSettings)
      _ <- setupQueueConnection(connectionSettings)
    } yield new MySqlMessageQueue(config.maxRetries)

  def setupQueueConnection(config: MySqlConnectionConfig): IO[Unit] = {
    val queueConnectionSettings = MySqlDataSourceSettings(config, "mysqlQueuePool")

    getDataSource(queueConnectionSettings).map { dataSource =>
      logger.error("configuring connection pool for queue")

      // note this is being called 2x in the case you use the mysql datastores and
      // loader. That should be ok
      DBs.loadGlobalSettings()

      // Configure the connection pool
      ConnectionPool.add(
        MySqlMessageQueue.QUEUE_CONNECTION_NAME,
        new DataSourceConnectionPool(dataSource, closer = new HikariCloser(dataSource))
      )

      logger.error("queue connection pool init complete")
    }
  }

} 
Example 5
Source File: DataStore.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.repository

import cats.effect.IO
import vinyldns.core.domain.batch.BatchChangeRepository
import vinyldns.core.domain.membership._
import vinyldns.core.domain.record.{RecordChangeRepository, RecordSetRepository}
import vinyldns.core.domain.zone.{ZoneChangeRepository, ZoneRepository}
import vinyldns.core.repository.RepositoryName.RepositoryName
import vinyldns.core.health.HealthCheck.HealthCheck
import vinyldns.core.task.TaskRepository

import scala.reflect.ClassTag

class LoadedDataStore(
    val dataStore: DataStore,
    val shutdownHook: IO[Unit],
    val healthCheck: HealthCheck
)

object DataStore {
  def apply(
      userRepository: Option[UserRepository] = None,
      groupRepository: Option[GroupRepository] = None,
      membershipRepository: Option[MembershipRepository] = None,
      groupChangeRepository: Option[GroupChangeRepository] = None,
      recordSetRepository: Option[RecordSetRepository] = None,
      recordChangeRepository: Option[RecordChangeRepository] = None,
      zoneChangeRepository: Option[ZoneChangeRepository] = None,
      zoneRepository: Option[ZoneRepository] = None,
      batchChangeRepository: Option[BatchChangeRepository] = None,
      userChangeRepository: Option[UserChangeRepository] = None,
      taskRepository: Option[TaskRepository] = None
  ): DataStore =
    new DataStore(
      userRepository,
      groupRepository,
      membershipRepository,
      groupChangeRepository,
      recordSetRepository,
      recordChangeRepository,
      zoneChangeRepository,
      zoneRepository,
      batchChangeRepository,
      userChangeRepository,
      taskRepository
    )
}

class DataStore(
    userRepository: Option[UserRepository] = None,
    groupRepository: Option[GroupRepository] = None,
    membershipRepository: Option[MembershipRepository] = None,
    groupChangeRepository: Option[GroupChangeRepository] = None,
    recordSetRepository: Option[RecordSetRepository] = None,
    recordChangeRepository: Option[RecordChangeRepository] = None,
    zoneChangeRepository: Option[ZoneChangeRepository] = None,
    zoneRepository: Option[ZoneRepository] = None,
    batchChangeRepository: Option[BatchChangeRepository] = None,
    userChangeRepository: Option[UserChangeRepository] = None,
    taskRepository: Option[TaskRepository] = None
) {

  lazy val dataStoreMap: Map[RepositoryName, Repository] =
    List(
      userRepository.map(RepositoryName.user -> _),
      groupRepository.map(RepositoryName.group -> _),
      membershipRepository.map(RepositoryName.membership -> _),
      groupChangeRepository.map(RepositoryName.groupChange -> _),
      recordSetRepository.map(RepositoryName.recordSet -> _),
      recordChangeRepository.map(RepositoryName.recordChange -> _),
      zoneChangeRepository.map(RepositoryName.zoneChange -> _),
      zoneRepository.map(RepositoryName.zone -> _),
      batchChangeRepository.map(RepositoryName.batchChange -> _),
      userChangeRepository.map(RepositoryName.userChange -> _),
      taskRepository.map(RepositoryName.task -> _)
    ).flatten.toMap

  def keys: Set[RepositoryName] = dataStoreMap.keySet

  def get[A <: Repository: ClassTag](name: RepositoryName): Option[A] =
    dataStoreMap.get(name).flatMap {
      case a: A => Some(a)
      case _ => None
    }
} 
Example 6
Source File: CryptoAlgebra.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.crypto

import cats.effect.IO
import com.typesafe.config.Config

trait CryptoAlgebra {
  def encrypt(value: String): String
  def decrypt(value: String): String
}

object CryptoAlgebra {
  def load(cryptoConfig: Config): IO[CryptoAlgebra] =
    for {
      className <- IO(cryptoConfig.getString("type"))
      classInstance <- IO(
        Class
          .forName(className)
          .getDeclaredConstructor(classOf[Config])
          .newInstance(cryptoConfig)
          .asInstanceOf[CryptoAlgebra]
      )
    } yield classInstance
} 
Example 7
Source File: HealthCheck.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.health

import cats.effect.IO
import org.slf4j.LoggerFactory

object HealthCheck {

  type HealthCheck = IO[Either[HealthCheckError, Unit]]

  case class HealthCheckError(message: String) extends Throwable(message)

  private val logger = LoggerFactory.getLogger("HealthCheck")

  implicit class HealthCheckImprovements(io: IO[Either[Throwable, _]]) {
    def asHealthCheck(caller: Class[_]): HealthCheck =
      io.map {
        case Left(err) =>
          logger.error(s"HealthCheck for ${caller.getCanonicalName} Failed", err)
          val msg = Option(err.getMessage).getOrElse("no message from error")
          Left(
            HealthCheckError(s"${caller.getCanonicalName} health check failed with msg='${msg}'")
          )
        case _ => Right(())
      }
  }

} 
Example 8
Source File: HealthService.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.health

import cats.effect.{ContextShift, IO}
import cats.implicits._
import org.slf4j.LoggerFactory
import vinyldns.core.health.HealthCheck.{HealthCheck, HealthCheckError}

class HealthService(healthChecks: List[HealthCheck]) {

  private val logger = LoggerFactory.getLogger(classOf[HealthService])

  private implicit val cs: ContextShift[IO] =
    IO.contextShift(scala.concurrent.ExecutionContext.global)

  def checkHealth(): IO[List[HealthCheckError]] =
    healthChecks.parSequence
      .map {
        _.collect {
          case Left(err) =>
            logger.error(s"Health Check Failure: ${err.message}")
            err
        }
      }
} 
Example 9
Source File: AllNotifiers.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.notifier

import cats.effect.{ContextShift, IO}
import cats.implicits._
import org.slf4j.LoggerFactory
import vinyldns.core.route.Monitored

final case class AllNotifiers(notifiers: List[Notifier])(implicit val cs: ContextShift[IO])
    extends Monitored {

  private val logger = LoggerFactory.getLogger("AllNotifiers")

  def notify(notification: Notification[_]): IO[Unit] =
    for {
      _ <- notifiers.parTraverse(notify(_, notification))
    } yield ()

  def notify(notifier: Notifier, notification: Notification[_]): IO[Unit] =
    monitor(notifier.getClass.getSimpleName) {
      notifier.notify(notification).handleErrorWith { e =>
        IO {
          logger.error(s"Notifier ${notifier.getClass.getSimpleName} failed.", e)
        }
      }
    }
} 
Example 10
Source File: NotifierLoader.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.notifier
import vinyldns.core.domain.membership.UserRepository
import cats.effect.IO
import cats.implicits._
import cats.effect.ContextShift

object NotifierLoader {

  def loadAll(configs: List[NotifierConfig], userRepository: UserRepository)(
      implicit cs: ContextShift[IO]
  ): IO[AllNotifiers] =
    for {
      notifiers <- configs.parTraverse(load(_, userRepository))
    } yield AllNotifiers(notifiers)

  def load(config: NotifierConfig, userRepository: UserRepository): IO[Notifier] =
    for {
      provider <- IO(
        Class
          .forName(config.className)
          .getDeclaredConstructor()
          .newInstance()
          .asInstanceOf[NotifierProvider]
      )
      notifier <- provider.load(config, userRepository)
    } yield notifier

} 
Example 11
Source File: MessageQueueLoader.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.queue

import cats.effect.IO
import org.slf4j.LoggerFactory

object MessageQueueLoader {

  private val logger = LoggerFactory.getLogger("MessageQueueLoader")

  def load(config: MessageQueueConfig): IO[MessageQueue] =
    for {
      _ <- IO(logger.error(s"Attempting to load queue ${config.className}"))
      provider <- IO(
        Class
          .forName(config.className)
          .getDeclaredConstructor()
          .newInstance()
          .asInstanceOf[MessageQueueProvider]
      )
      queue <- provider.load(config)
    } yield queue
} 
Example 12
Source File: MockDataStoreProvider.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.repository

import cats.effect.IO
import org.scalatestplus.mockito.MockitoSugar
import vinyldns.core.crypto.CryptoAlgebra
import vinyldns.core.domain.batch.BatchChangeRepository
import vinyldns.core.domain.membership.{
  GroupChangeRepository,
  GroupRepository,
  MembershipRepository,
  UserRepository
}
import vinyldns.core.domain.record.{RecordChangeRepository, RecordSetRepository}
import vinyldns.core.domain.zone.{ZoneChangeRepository, ZoneRepository}
import vinyldns.core.health.HealthCheck.HealthCheck

class MockDataStoreProvider extends DataStoreProvider with MockitoSugar {

  def load(config: DataStoreConfig, crypto: CryptoAlgebra): IO[LoadedDataStore] = {
    val repoConfig = config.repositories

    val user = repoConfig.user.map(_ => mock[UserRepository])
    val group = repoConfig.group.map(_ => mock[GroupRepository])
    val membership = repoConfig.membership.map(_ => mock[MembershipRepository])
    val groupChange = repoConfig.groupChange.map(_ => mock[GroupChangeRepository])
    val recordSet = repoConfig.recordSet.map(_ => mock[RecordSetRepository])
    val recordChange = repoConfig.recordChange.map(_ => mock[RecordChangeRepository])
    val zoneChange = repoConfig.zoneChange.map(_ => mock[ZoneChangeRepository])
    val zone = repoConfig.zone.map(_ => mock[ZoneRepository])
    val batchChange = repoConfig.batchChange.map(_ => mock[BatchChangeRepository])

    IO.pure(
      new LoadedDataStore(
        DataStore(
          user,
          group,
          membership,
          groupChange,
          recordSet,
          recordChange,
          zoneChange,
          zone,
          batchChange
        ),
        IO.unit,
        checkHealth()
      )
    )
  }

  def checkHealth(): HealthCheck = IO.pure(Right((): Unit))
}

class AlternateMockDataStoreProvider extends MockDataStoreProvider {

  override def load(config: DataStoreConfig, crypto: CryptoAlgebra): IO[LoadedDataStore] =
    IO.pure(new LoadedDataStore(DataStore(), shutdown(), checkHealth()))

  def shutdown(): IO[Unit] = IO.raiseError(new RuntimeException("oh no"))
}

class FailDataStoreProvider extends DataStoreProvider {
  def load(config: DataStoreConfig, crypto: CryptoAlgebra): IO[LoadedDataStore] =
    IO.raiseError(new RuntimeException("ruh roh"))
} 
Example 13
Source File: AllNotifiersSpec.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.notifier

import cats.scalatest.{EitherMatchers, EitherValues, ValidatedMatchers}
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito._
import cats.effect.IO
import org.scalatest.BeforeAndAfterEach
import cats.effect.ContextShift
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

class AllNotifiersSpec
    extends AnyWordSpec
    with Matchers
    with MockitoSugar
    with EitherValues
    with EitherMatchers
    with ValidatedMatchers
    with BeforeAndAfterEach {

  implicit val cs: ContextShift[IO] = IO.contextShift(scala.concurrent.ExecutionContext.global)

  val mockNotifiers = List.fill(3)(mock[Notifier])

  val notification = Notification("anything")

  override def beforeEach: Unit =
    mockNotifiers.foreach { mock =>
      reset(mock)
      when(mock.notify(notification)).thenReturn(IO.unit)
    }

  "notifier" should {
    "notify all contained notifiers" in {

      val notifier = AllNotifiers(mockNotifiers)

      notifier.notify(notification)

      mockNotifiers.foreach(verify(_).notify(notification))
    }

    "suppress errors from notifiers" in {
      val notifier = AllNotifiers(mockNotifiers)

      when(mockNotifiers(2).notify(notification)).thenReturn(IO.raiseError(new Exception("fail")))

      notifier.notify(notification).unsafeRunSync()

      mockNotifiers.foreach(verify(_).notify(notification))
    }
  }

} 
Example 14
Source File: MessageQueueLoaderSpec.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.queue

import cats.effect.IO
import com.typesafe.config.{Config, ConfigFactory}
import org.scalatestplus.mockito.MockitoSugar

import scala.concurrent.duration._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

object MockMessageQueueProvider extends MockitoSugar {

  val mockMessageQueue: MessageQueue = mock[MessageQueue]

}

class MockMessageQueueProvider extends MessageQueueProvider {

  def load(config: MessageQueueConfig): IO[MessageQueue] =
    IO.pure(MockMessageQueueProvider.mockMessageQueue)

}

class FailMessageQueueProvider extends MessageQueueProvider {

  def load(config: MessageQueueConfig): IO[MessageQueue] =
    IO.raiseError(new RuntimeException("boo"))

}

class MessageQueueLoaderSpec extends AnyWordSpec with Matchers {

  val placeholderConfig: Config = ConfigFactory.parseString("{}")
  private val pollingInterval = 250.millis
  private val messagesPerPoll = 10

  "load" should {
    "return the correct queue if properly configured" in {
      val config =
        MessageQueueConfig(
          "vinyldns.core.queue.MockMessageQueueProvider",
          pollingInterval,
          messagesPerPoll,
          placeholderConfig,
          100
        )

      val loadCall = MessageQueueLoader.load(config)
      loadCall.unsafeRunSync() shouldBe MockMessageQueueProvider.mockMessageQueue
    }
    "Error if the configured provider cannot be found" in {
      val config =
        MessageQueueConfig("bad.class", pollingInterval, messagesPerPoll, placeholderConfig, 100)

      val loadCall = MessageQueueLoader.load(config)

      a[ClassNotFoundException] shouldBe thrownBy(loadCall.unsafeRunSync())
    }
    "Error if an error is returned from external load" in {
      val config =
        MessageQueueConfig(
          "vinyldns.core.queue.FailMessageQueueProvider",
          pollingInterval,
          messagesPerPoll,
          placeholderConfig,
          100
        )

      val loadCall = MessageQueueLoader.load(config)

      a[RuntimeException] shouldBe thrownBy(loadCall.unsafeRunSync())
    }
  }
} 
Example 15
Source File: TaskSchedulerSpec.scala    From vinyldns   with Apache License 2.0 5 votes vote down vote up
package vinyldns.core.task
import cats.effect.{ContextShift, IO, Timer}
import org.mockito.Mockito
import org.mockito.Mockito._
import org.scalatestplus.mockito.MockitoSugar
import org.scalatest.BeforeAndAfterEach

import scala.concurrent.duration._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

class TaskSchedulerSpec
    extends AnyWordSpec
    with Matchers
    with MockitoSugar
    with BeforeAndAfterEach {

  private implicit val cs: ContextShift[IO] =
    IO.contextShift(scala.concurrent.ExecutionContext.global)
  private implicit val timer: Timer[IO] = IO.timer(scala.concurrent.ExecutionContext.global)

  private val mockRepo = mock[TaskRepository]

  class TestTask(
      val name: String,
      val timeout: FiniteDuration,
      val runEvery: FiniteDuration,
      val checkInterval: FiniteDuration,
      testResult: IO[Unit] = IO.unit
  ) extends Task {
    def run(): IO[Unit] = testResult
  }

  override def beforeEach() = Mockito.reset(mockRepo)

  "TaskScheduler" should {
    "run a scheduled task" in {
      val task = new TestTask("test", 5.seconds, 500.millis, 500.millis)
      val spied = spy(task)
      doReturn(IO.unit).when(mockRepo).saveTask(task.name)
      doReturn(IO.pure(true)).when(mockRepo).claimTask(task.name, task.timeout, task.runEvery)
      doReturn(IO.unit).when(mockRepo).releaseTask(task.name)

      TaskScheduler.schedule(spied, mockRepo).take(1).compile.drain.unsafeRunSync()

      // We run twice because we run once on start up
      verify(spied, times(2)).run()
      verify(mockRepo, times(2)).claimTask(task.name, task.timeout, task.runEvery)
      verify(mockRepo, times(2)).releaseTask(task.name)
    }

    "release the task even on error" in {
      val task =
        new TestTask(
          "test",
          5.seconds,
          500.millis,
          500.millis,
          IO.raiseError(new RuntimeException("fail"))
        )
      doReturn(IO.unit).when(mockRepo).saveTask(task.name)
      doReturn(IO.pure(true)).when(mockRepo).claimTask(task.name, task.timeout, task.runEvery)
      doReturn(IO.unit).when(mockRepo).releaseTask(task.name)

      TaskScheduler.schedule(task, mockRepo).take(1).compile.drain.unsafeRunSync()

      // We release the task twice, once on start and once on the run
      verify(mockRepo, times(2)).releaseTask(task.name)
    }

    "fail to start if the task cannot be saved" in {
      val task = new TestTask("test", 5.seconds, 500.millis, 500.millis)
      val spied = spy(task)
      doReturn(IO.raiseError(new RuntimeException("fail"))).when(mockRepo).saveTask(task.name)

      a[RuntimeException] should be thrownBy TaskScheduler
        .schedule(task, mockRepo)
        .take(1)
        .compile
        .drain
        .unsafeRunSync()
      verify(spied, never()).run()
    }
  }
} 
Example 16
Source File: KafkaTest.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.kafkadistributedprocessing

import java.util.Properties

import aecor.kafkadistributedprocessing.internal.Kafka.UnitDeserializer
import aecor.kafkadistributedprocessing.internal.RebalanceEvents.RebalanceEvent
import aecor.kafkadistributedprocessing.internal.RebalanceEvents.RebalanceEvent.{
  PartitionsAssigned,
  PartitionsRevoked
}
import aecor.kafkadistributedprocessing.internal.{ Kafka, KafkaConsumer }
import cats.effect.IO
import cats.implicits._
import fs2.Stream
import fs2.concurrent.Queue
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.scalatest.funsuite.AnyFunSuite

import scala.concurrent.duration._

class KafkaTest extends AnyFunSuite with IOSupport with KafkaSupport {
  val topic = "test"
  val partitionCount = 4

  createCustomTopic(topic, partitions = partitionCount)

  val createConsumerAccess = {
    val properties = new Properties()
    properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers.mkString(","))
    properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "test")
    KafkaConsumer.create[IO](properties, new UnitDeserializer, new UnitDeserializer)
  }

  val watchRebalanceEvents =
    Stream
      .resource(createConsumerAccess)
      .flatMap(Kafka.watchRebalanceEvents(_, topic, 500.millis, 50.millis))

  test("Rebalance event stream") {

    val program = for {
      queue <- Queue.unbounded[IO, (Int, RebalanceEvent)]

      run = (n: Int) =>
        watchRebalanceEvents
          .evalMap { x =>
            val e = n -> x.value
            queue.enqueue1(e) >> x.commit
          }
          .compile
          .drain
          .start

      p1 <- run(1)

      l1 <- queue.dequeue.take(2).compile.toList

      p2 <- run(2)

      l2 <- queue.dequeue.take(4).compile.toList

      _ <- p1.cancel

      l3 <- queue.dequeue.take(2).compile.toList

      _ <- p2.cancel

    } yield (l1, l2, l3)

    val (l1, l2, l3) = program.unsafeRunTimed(40.seconds).get

    def fold(list: List[(Int, RebalanceEvent)]): Map[Int, Set[Int]] =
      list.foldLeft(Map.empty[Int, Set[Int]]) {
        case (s, (c, e)) =>
          e match {
            case PartitionsRevoked(partitions) =>
              s.updated(c, s.getOrElse(c, Set.empty[Int]) -- partitions.map(_.partition()))
            case PartitionsAssigned(partitions) =>
              s.updated(c, s.getOrElse(c, Set.empty[Int]) ++ partitions.map(_.partition()))
          }
      }

    assert(fold(l1) == Map(1 -> Set(1, 0, 3, 2)))
    assert(fold(l2) == Map(1 -> Set(1, 0), 2 -> Set(2, 3)))
    assert(fold(l3) == Map(2 -> Set(1, 0, 3, 2)))

  }

  test("Topic partitions query works before subscription") {
    val program = createConsumerAccess.use(_.partitionsFor(topic))
    val result = program.unsafeRunTimed(2.seconds).get
    assert(result.size == partitionCount)
  }

} 
Example 17
Source File: ChannelTest.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.kafkadistributedprocessing

import aecor.kafkadistributedprocessing.internal.Channel
import cats.effect.IO
import cats.implicits._
import fs2.concurrent.Queue
import org.scalatest.funsuite.AnyFunSuite

import scala.concurrent.duration._

class ChannelTest extends AnyFunSuite with IOSupport {
  test("Channel#call completes only after completion callback") {
    val out = Channel
      .create[IO]
      .flatMap {
        case Channel(watch, _, call) =>
          Queue.unbounded[IO, String].flatMap { queue =>
            for {
              fiber <- watch.flatMap { callback =>
                        queue.enqueue1("before callback") >> callback
                      }.start
              _ <- queue.enqueue1("before call") >> call >> queue.enqueue1("after call")
              _ <- fiber.join
              out <- queue.dequeue.take(3).compile.toList
            } yield out
          }
      }
      .unsafeRunTimed(1.seconds)
      .get
    assert(out == List("before call", "before callback", "after call"))
  }

  test("Channel#call does not wait for completion callback if channel is closed") {

    Channel
      .create[IO]
      .flatMap {
        case Channel(watch, close, call) =>
          for {
            w <- watch.start
            c <- call.start
            _ <- close
            _ <- c.join
            _ <- w.cancel
          } yield ()
      }
      .replicateA(100)
      .unsafeRunTimed(10.seconds)
      .get
    assert(true)
  }
} 
Example 18
Source File: KafkaDistributedProcessingTest.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.kafkadistributedprocessing

import cats.effect.concurrent.{ Deferred, Ref }
import cats.effect.{ ExitCase, IO }
import cats.implicits._
import fs2.concurrent.Queue
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.scalatest.funsuite.AnyFunSuiteLike
import scala.concurrent.duration._
class KafkaDistributedProcessingTest extends AnyFunSuiteLike with KafkaSupport with IOSupport {

  val topicName = "process-distribution"

  createCustomTopic(topicName, partitions = 4)

  val settings =
    DistributedProcessingSettings(Set(s"localhost:${kafkaConfig.kafkaPort}"), topicName)

  test("Process error propagation") {
    val exception = new RuntimeException("Oops!")

    val result = DistributedProcessing(settings)
      .start("Process error propagation", List(IO.raiseError[Unit](exception)))
      .attempt
      .timeout(20.seconds)
      .unsafeRunSync()

    assert(result == Left(exception))
  }

  test("Process lifecycle") {

    val test = Ref.of[IO, (Boolean, Boolean)]((false, false)).flatMap { ref =>
      Deferred[IO, Unit]
        .flatMap { done =>
          val process =
            ref.set((true, false)) >>
              done.complete(()) >>
              IO.never.guaranteeCase {
                case ExitCase.Canceled => ref.set((true, true))
                case _                 => IO.unit
              }.void

          val run = DistributedProcessing(settings)
            .start("Process lifecycle", List(process))

          IO.race(run, done.get) >> ref.get
        }
    }

    val (started, finished) = test.timeout(20.seconds).unsafeRunSync()

    assert(started)
    assert(finished)
  }

  test("Process distribution") {
    val test = Queue.unbounded[IO, Int].flatMap { queue =>
      def run(client: Int) =
        DistributedProcessing(
          settings.withConsumerSetting(ConsumerConfig.CLIENT_ID_CONFIG, client.toString)
        ).start(
          "Process distribution",
          Stream
            .from(0)
            .take(8)
            .map { n =>
              val idx = client * 10 + n
              (queue.enqueue1(idx) >> IO.cancelBoundary <* IO.never)
                .guarantee(queue.enqueue1(-idx))
            }
            .toList
        )

      def dequeue(size: Long): IO[List[Int]] =
        queue.dequeue.take(size).compile.to[List]

      for {
        d1 <- run(1).start
        s1 <- dequeue(8)
        d2 <- run(2).start
        s2 <- dequeue(16)
        _ <- d1.cancel
        s3 <- dequeue(16)
        _ <- d2.cancel
        s4 <- dequeue(8)
      } yield (s1, s2, s3, s4)
    }

    val (s1, s2, s3, s4) = test.timeout(20.seconds).unsafeRunSync()

    assert(s1.toSet == Set(10, 11, 12, 13, 14, 15, 16, 17))
    assert((s1 ++ s2 ++ s3 ++ s4).sum == 0)
  }

} 
Example 19
Source File: E2eSupport.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.testkit

import aecor.data.{ EventsourcedBehavior, _ }
import aecor.runtime.{ EventJournal, Eventsourced }
import cats.data.StateT
import cats.effect.{ IO, Sync }
import cats.implicits._
import cats.mtl.MonadState
import cats.tagless.FunctorK
import cats.tagless.syntax.functorK._
import cats.~>
import fs2.Stream
import monocle.Lens

import scala.collection.immutable._

object E2eSupport {

  final class Runtime[F[_]] {
    def deploy[M[_[_]]: FunctorK, S, E, K](
      behavior: EventsourcedBehavior[M, F, S, E],
      journal: EventJournal[F, K, E]
    )(implicit F: Sync[F]): K => M[F] =
      Eventsourced[M, F, S, E, K](behavior, journal)
  }

  abstract class Processes[F[_]](items: Vector[F[Unit]]) {
    protected type S
    protected implicit def F: MonadState[F, S]

    final private implicit val monad = F.monad

    final def runProcesses: F[Unit] =
      for {
        stateBefore <- F.get
        _ <- items.sequence
        stateAfter <- F.get
        _ <- if (stateAfter == stateBefore) {
              ().pure[F]
            } else {
              runProcesses
            }
      } yield ()

    final def wireK[I, M[_[_]]: FunctorK](behavior: I => M[F]): I => M[F] =
      i =>
        behavior(i).mapK(new (F ~> F) {
          override def apply[A](fa: F[A]): F[A] =
            fa <* runProcesses
        })

    final def wire[A, B](f: F[B]): F[B] =
      f <* runProcesses
  }

  object Processes {
    def apply[F[_], S0](items: F[Unit]*)(implicit F0: MonadState[F, S0]): Processes[F] =
      new Processes[F](items.toVector) {
        final override type S = S0
        override implicit def F: MonadState[F, S] = F0
      }
  }
}

trait E2eSupport {
  import cats.mtl.instances.state._

  type SpecState

  type F[A] = StateT[IO, SpecState, A]

  final def mkJournal[I, E](lens: Lens[SpecState, StateEventJournal.State[I, E]],
                            tagging: Tagging[I]): StateEventJournal[F, I, SpecState, E] =
    StateEventJournal[F, I, SpecState, E](lens, tagging)

  final def wireProcess[In](process: In => F[Unit],
                            source: Stream[F, Committable[F, In]],
                            sources: Stream[F, Committable[F, In]]*)(implicit F: Sync[F]): F[Unit] =
    sources
      .fold(source)(_ ++ _)
      .evalMap(_.process(process))
      .compile
      .drain

  val runtime = new E2eSupport.Runtime[F]
} 
Example 20
Source File: AkkaPersistenceRuntimeSpec.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.tests

import aecor.data.Tagging
import aecor.runtime.akkapersistence.{ AkkaPersistenceRuntime, CassandraJournalAdapter }
import aecor.tests.e2e._
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import akka.testkit.TestKit
import cats.effect.IO
import cats.implicits._
import com.typesafe.config.{ Config, ConfigFactory }
import org.scalatest.matchers.should.Matchers
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.funsuite.AnyFunSuiteLike

import scala.concurrent.duration._

object AkkaPersistenceRuntimeSpec {
  def conf: Config = ConfigFactory.parseString(s"""
        akka {
          cluster {
            seed-nodes = [
              "akka.tcp://[email protected]:52000"
            ]
          }
          actor.provider = cluster
          remote {
            netty.tcp {
              hostname = 127.0.0.1
              port = 52000
              bind.hostname = "0.0.0.0"
              bind.port = 52000
            }
          }
        }
        aecor.generic-akka-runtime.idle-timeout = 1s
     """).withFallback(CassandraLifecycle.config).withFallback(ConfigFactory.load())
}

class AkkaPersistenceRuntimeSpec
    extends TestKit(ActorSystem("test", AkkaPersistenceRuntimeSpec.conf))
    with AnyFunSuiteLike
    with Matchers
    with ScalaFutures
    with CassandraLifecycle {

  override def systemName = system.name

  override implicit val patienceConfig = PatienceConfig(30.seconds, 150.millis)

  val timer = IO.timer(system.dispatcher)
  implicit val contextShift = IO.contextShift(system.dispatcher)
  override def afterAll(): Unit = {
    TestKit.shutdownActorSystem(system)
    super.afterAll()
  }

  val runtime = AkkaPersistenceRuntime(system, CassandraJournalAdapter(system))

  test("Runtime should work") {
    val deployCounters: IO[CounterId => Counter[IO]] =
      runtime.deploy(
        "Counter",
        CounterBehavior.instance[IO],
        Tagging.const[CounterId](CounterEvent.tag)
      )
    val program = for {
      counters <- deployCounters
      first = counters(CounterId("1"))
      second = counters(CounterId("2"))
      _ <- first.increment
      _ <- second.increment
      _2 <- second.value
      _ <- first.decrement
      _1 <- first.value
      afterPassivation <- timer.sleep(2.seconds) >> second.value
    } yield (_1, _2, afterPassivation)

    program.unsafeRunSync() shouldEqual ((0L, 1L, 1L))
  }
  test("Journal should work") {
    implicit val materializer = ActorMaterializer()
    val journal = runtime.journal[CounterId, CounterEvent]
    val entries = journal.currentEventsByTag(CounterEvent.tag, None).runWith(Sink.seq).futureValue

    val map = entries.map(_.event).groupBy(_.entityKey)
    map(CounterId("1")).size shouldBe 2
    map(CounterId("2")).size shouldBe 1
  }
} 
Example 21
Source File: GenericRuntimeSpec.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.runtime.akkageneric

import akka.actor.ActorSystem
import akka.testkit.TestKit
import cats.effect.IO
import cats.implicits._
import com.typesafe.config.{ Config, ConfigFactory }
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.funsuite.AnyFunSuiteLike
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers

import scala.concurrent.duration._

object GenericRuntimeSpec {
  def conf: Config = ConfigFactory.parseString(s"""
        cluster.system-name=test
        cluster.port = 51001
        aecor.generic-akka-runtime.idle-timeout = 1s
     """).withFallback(ConfigFactory.load())
}

class GenericRuntimeSpec
    extends TestKit(ActorSystem("test", GenericRuntimeSpec.conf))
    with AnyFunSuiteLike
    with Matchers
    with ScalaFutures
    with BeforeAndAfterAll {

  implicit val contextShift = IO.contextShift(system.dispatcher)

  override implicit val patienceConfig = PatienceConfig(15.seconds, 150.millis)

  val timer = IO.timer(system.dispatcher)

  override def afterAll: Unit =
    TestKit.shutdownActorSystem(system)

  def runCounters(name: String): IO[CounterId => Counter[IO]] =
    GenericAkkaRuntime(system)
      .runBehavior[CounterId, Counter, IO](name, (_: CounterId) => Counter.inmem[IO])

  test("routing") {
    val program = for {
      counters <- runCounters("CounterFoo")
      first = counters(CounterId("1"))
      second = counters(CounterId("2"))
      _ <- first.increment
      _2 <- second.increment
      _1 <- first.increment
    } yield (_1, _2)

    val (first, second) = program.unsafeRunSync()
    first shouldBe 2L
    second shouldBe 1L
  }

  test("passivation") {
    val program = for {
      counters <- runCounters("CounterBar")
      first = counters(CounterId("1"))
      _1 <- first.increment
      afterPassivation <- timer.sleep(2.seconds) >> first.value
    } yield (_1, afterPassivation)

    val (beforePassivation, afterPassivation) = program.unsafeRunSync()
    beforePassivation shouldBe 1
    afterPassivation shouldBe 0
  }
} 
Example 22
Source File: effect.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.util

import cats.effect.{ Async, Effect, IO }

import scala.concurrent.{ Future, Promise }

object effect {
  implicit final class AecorEffectOps[F[_], A](val self: F[A]) extends AnyVal {
    @inline final def unsafeToFuture()(implicit F: Effect[F]): Future[A] = {
      val p = Promise[A]
      F.runAsync(self) {
          case Right(a) => IO { p.success(a); () }
          case Left(e)  => IO { p.failure(e); () }
        }
        .unsafeRunSync()
      p.future
    }
  }

  implicit final class AecorLiftIOOps[F[_]](val self: Async[F]) extends AnyVal {
    def fromFuture[A](future: => Future[A]): F[A] =
      IO.fromFuture(IO(future))(IO.contextShift(scala.concurrent.ExecutionContext.global)).to(self)
  }
} 
Example 23
Source File: JobsApiTest.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.{ConcurrentEffect, IO}
import com.goyeau.kubernetes.client.operation._
import com.goyeau.kubernetes.client.KubernetesClient
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import io.k8s.api.batch.v1.{Job, JobList, JobSpec}
import io.k8s.api.core.v1._
import io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
import org.scalatest.OptionValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class JobsApiTest
    extends AnyFlatSpec
    with Matchers
    with OptionValues
    with CreatableTests[IO, Job]
    with GettableTests[IO, Job]
    with ListableTests[IO, Job, JobList]
    with ReplaceableTests[IO, Job]
    with DeletableTests[IO, Job, JobList]
    with DeletableTerminatedTests[IO, Job, JobList]
    with WatchableTests[IO, Job]
    with ContextProvider {

  implicit lazy val F: ConcurrentEffect[IO] = IO.ioConcurrentEffect
  implicit lazy val logger: Logger[IO]      = Slf4jLogger.getLogger[IO]
  lazy val resourceName                     = classOf[Job].getSimpleName

  override def api(implicit client: KubernetesClient[IO]) = client.jobs
  override def namespacedApi(namespaceName: String)(implicit client: KubernetesClient[IO]) =
    client.jobs.namespace(namespaceName)

  override def sampleResource(resourceName: String, labels: Map[String, String]) =
    Job(
      metadata = Option(ObjectMeta(name = Option(resourceName), labels = Option(labels))),
      spec = Option(
        JobSpec(
          template = PodTemplateSpec(
            metadata = Option(ObjectMeta(name = Option(resourceName))),
            spec = Option(
              PodSpec(containers = Seq(Container("test", image = Option("docker"))), restartPolicy = Option("Never"))
            )
          )
        )
      )
    )
  val labels = Map("app" -> "test")
  override def modifyResource(resource: Job) = resource.copy(
    metadata = Option(ObjectMeta(name = resource.metadata.flatMap(_.name), labels = Option(labels)))
  )
  override def checkUpdated(updatedResource: Job) =
    (updatedResource.metadata.value.labels.value.toSeq should contain).allElementsOf(labels.toSeq)

  override def deleteApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Deletable[IO] =
    client.jobs.namespace(namespaceName)

  override def watchApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Watchable[IO, Job] =
    client.jobs.namespace(namespaceName)
} 
Example 24
Source File: DeploymentsApiTest.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.{ConcurrentEffect, IO}
import com.goyeau.kubernetes.client.operation._
import com.goyeau.kubernetes.client.{IntValue, KubernetesClient, StringValue}
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import io.k8s.api.apps.v1._
import io.k8s.api.core.v1._
import io.k8s.apimachinery.pkg.apis.meta.v1.{LabelSelector, ObjectMeta}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers

class DeploymentsApiTest
    extends AnyFlatSpec
    with Matchers
    with OptionValues
    with CreatableTests[IO, Deployment]
    with GettableTests[IO, Deployment]
    with ListableTests[IO, Deployment, DeploymentList]
    with ReplaceableTests[IO, Deployment]
    with DeletableTests[IO, Deployment, DeploymentList]
    with DeletableTerminatedTests[IO, Deployment, DeploymentList]
    with WatchableTests[IO, Deployment]
    with ContextProvider {

  implicit lazy val F: ConcurrentEffect[IO] = IO.ioConcurrentEffect
  implicit lazy val logger: Logger[IO]      = Slf4jLogger.getLogger[IO]
  lazy val resourceName                     = classOf[Deployment].getSimpleName

  override def api(implicit client: KubernetesClient[IO]) = client.deployments
  override def namespacedApi(namespaceName: String)(implicit client: KubernetesClient[IO]) =
    client.deployments.namespace(namespaceName)

  override def sampleResource(resourceName: String, labels: Map[String, String]) = {
    val label = Option(Map("app" -> "test"))
    Deployment(
      metadata = Option(ObjectMeta(name = Option(resourceName), labels = Option(labels))),
      spec = Option(
        DeploymentSpec(
          selector = LabelSelector(matchLabels = label),
          template = PodTemplateSpec(
            metadata = Option(ObjectMeta(name = Option(resourceName), labels = label)),
            spec = Option(PodSpec(containers = Seq(Container("test", image = Option("docker")))))
          )
        )
      )
    )
  }
  val strategy = Option(
    DeploymentStrategy(
      `type` = Option("RollingUpdate"),
      rollingUpdate =
        Option(RollingUpdateDeployment(maxSurge = Option(StringValue("25%")), maxUnavailable = Option(IntValue(10))))
    )
  )
  override def modifyResource(resource: Deployment) = resource.copy(
    metadata = Option(ObjectMeta(name = resource.metadata.flatMap(_.name))),
    spec = resource.spec.map(_.copy(strategy = strategy))
  )
  override def checkUpdated(updatedResource: Deployment) =
    updatedResource.spec.value.strategy shouldBe strategy

  override def deleteApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Deletable[IO] =
    client.deployments.namespace(namespaceName)

  override def watchApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Watchable[IO, Deployment] =
    client.deployments.namespace(namespaceName)
} 
Example 25
Source File: CronJobsApiTest.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.{ConcurrentEffect, IO}
import com.goyeau.kubernetes.client.operation._
import com.goyeau.kubernetes.client.KubernetesClient
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import io.k8s.api.batch.v1.JobSpec
import io.k8s.api.batch.v1beta1.{CronJob, CronJobList, CronJobSpec, JobTemplateSpec}
import io.k8s.api.core.v1._
import io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers

class CronJobsApiTest
    extends AnyFlatSpec
    with Matchers
    with OptionValues
    with CreatableTests[IO, CronJob]
    with GettableTests[IO, CronJob]
    with ListableTests[IO, CronJob, CronJobList]
    with ReplaceableTests[IO, CronJob]
    with DeletableTests[IO, CronJob, CronJobList]
    with DeletableTerminatedTests[IO, CronJob, CronJobList]
    with WatchableTests[IO, CronJob]
    with ContextProvider {

  implicit lazy val F: ConcurrentEffect[IO] = IO.ioConcurrentEffect
  implicit lazy val logger: Logger[IO]      = Slf4jLogger.getLogger[IO]
  lazy val resourceName                     = classOf[CronJob].getSimpleName

  override def api(implicit client: KubernetesClient[IO]) = client.cronJobs
  override def namespacedApi(namespaceName: String)(implicit client: KubernetesClient[IO]) =
    client.cronJobs.namespace(namespaceName)

  override def sampleResource(resourceName: String, labels: Map[String, String]) =
    CronJob(
      metadata = Option(ObjectMeta(name = Option(resourceName), labels = Option(labels))),
      spec = Option(
        CronJobSpec(
          schedule = "1 * * * *",
          jobTemplate = JobTemplateSpec(
            spec = Option(
              JobSpec(
                template = PodTemplateSpec(
                  metadata = Option(ObjectMeta(name = Option(resourceName))),
                  spec = Option(
                    PodSpec(
                      containers = Seq(Container("test", image = Option("docker"))),
                      restartPolicy = Option("Never")
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  val schedule = "2 * * * *"
  override def modifyResource(resource: CronJob) = resource.copy(
    metadata = Option(ObjectMeta(name = resource.metadata.flatMap(_.name))),
    spec = resource.spec.map(_.copy(schedule = schedule))
  )
  override def checkUpdated(updatedResource: CronJob) =
    updatedResource.spec.value.schedule shouldBe schedule

  override def deleteApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Deletable[IO] =
    client.cronJobs.namespace(namespaceName)

  override def watchApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Watchable[IO, CronJob] =
    client.cronJobs.namespace(namespaceName)
} 
Example 26
Source File: ReplicaSetsApiTest.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.{ConcurrentEffect, IO}
import com.goyeau.kubernetes.client.KubernetesClient
import com.goyeau.kubernetes.client.operation._
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import io.k8s.api.apps.v1._
import io.k8s.api.core.v1._
import io.k8s.apimachinery.pkg.apis.meta.v1.{LabelSelector, ObjectMeta}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers

class ReplicaSetsApiTest
    extends AnyFlatSpec
    with Matchers
    with OptionValues
    with CreatableTests[IO, ReplicaSet]
    with GettableTests[IO, ReplicaSet]
    with ListableTests[IO, ReplicaSet, ReplicaSetList]
    with ReplaceableTests[IO, ReplicaSet]
    with DeletableTests[IO, ReplicaSet, ReplicaSetList]
    with DeletableTerminatedTests[IO, ReplicaSet, ReplicaSetList]
    with WatchableTests[IO, ReplicaSet]
    with ContextProvider {

  implicit lazy val F: ConcurrentEffect[IO] = IO.ioConcurrentEffect
  implicit lazy val logger: Logger[IO]      = Slf4jLogger.getLogger[IO]
  lazy val resourceName                     = classOf[ReplicaSet].getSimpleName

  override def api(implicit client: KubernetesClient[IO]) = client.replicaSets
  override def namespacedApi(namespaceName: String)(implicit client: KubernetesClient[IO]) =
    client.replicaSets.namespace(namespaceName)

  override def sampleResource(resourceName: String, labels: Map[String, String]) = {
    val label = Option(Map("app" -> "test"))
    ReplicaSet(
      metadata = Option(ObjectMeta(name = Option(resourceName), labels = Option(labels))),
      spec = Option(
        ReplicaSetSpec(
          selector = LabelSelector(matchLabels = label),
          template = Option(
            PodTemplateSpec(
              metadata = Option(ObjectMeta(name = Option(resourceName), labels = label)),
              spec = Option(PodSpec(containers = Seq(Container("test", image = Option("docker")))))
            )
          )
        )
      )
    )
  }
  val replicas = Option(5)
  override def modifyResource(resource: ReplicaSet) = resource.copy(
    metadata = Option(ObjectMeta(name = resource.metadata.flatMap(_.name))),
    spec = resource.spec.map(_.copy(replicas = replicas))
  )
  override def checkUpdated(updatedResource: ReplicaSet) =
    updatedResource.spec.value.replicas shouldBe replicas

  override def deleteApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Deletable[IO] =
    client.replicaSets.namespace(namespaceName)

  override def watchApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Watchable[IO, ReplicaSet] =
    client.replicaSets.namespace(namespaceName)
} 
Example 27
Source File: StatefulSetsApiTest.scala    From kubernetes-client   with Apache License 2.0 5 votes vote down vote up
package com.goyeau.kubernetes.client.api

import cats.effect.{ConcurrentEffect, IO}
import com.goyeau.kubernetes.client.KubernetesClient
import com.goyeau.kubernetes.client.operation._
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import io.k8s.api.apps.v1._
import io.k8s.api.core.v1._
import io.k8s.apimachinery.pkg.apis.meta.v1.{LabelSelector, ObjectMeta}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers

class StatefulSetsApiTest
    extends AnyFlatSpec
    with Matchers
    with OptionValues
    with CreatableTests[IO, StatefulSet]
    with GettableTests[IO, StatefulSet]
    with ListableTests[IO, StatefulSet, StatefulSetList]
    with ReplaceableTests[IO, StatefulSet]
    with DeletableTests[IO, StatefulSet, StatefulSetList]
    with DeletableTerminatedTests[IO, StatefulSet, StatefulSetList]
    with WatchableTests[IO, StatefulSet]
    with ContextProvider {

  implicit lazy val F: ConcurrentEffect[IO] = IO.ioConcurrentEffect
  implicit lazy val logger: Logger[IO]      = Slf4jLogger.getLogger[IO]
  lazy val resourceName                     = classOf[StatefulSet].getSimpleName

  override def api(implicit client: KubernetesClient[IO]) = client.statefulSets
  override def namespacedApi(namespaceName: String)(implicit client: KubernetesClient[IO]) =
    client.statefulSets.namespace(namespaceName)

  override def sampleResource(resourceName: String, labels: Map[String, String]) = {
    val label = Option(Map("app" -> "test"))
    StatefulSet(
      metadata = Option(ObjectMeta(name = Option(resourceName), labels = Option(labels))),
      spec = Option(
        StatefulSetSpec(
          serviceName = "service-name",
          selector = LabelSelector(matchLabels = label),
          template = PodTemplateSpec(
            metadata = Option(ObjectMeta(name = Option(resourceName), labels = label)),
            spec = Option(PodSpec(containers = Seq(Container("test", image = Option("docker")))))
          )
        )
      )
    )
  }
  val updateStrategy = Option(
    StatefulSetUpdateStrategy(
      `type` = Option("RollingUpdate"),
      rollingUpdate = Option(RollingUpdateStatefulSetStrategy(partition = Option(10)))
    )
  )
  override def modifyResource(resource: StatefulSet) = resource.copy(
    metadata = Option(ObjectMeta(name = resource.metadata.flatMap(_.name))),
    spec = resource.spec.map(_.copy(updateStrategy = updateStrategy))
  )
  override def checkUpdated(updatedResource: StatefulSet) =
    updatedResource.spec.value.updateStrategy shouldBe updateStrategy

  override def deleteApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Deletable[IO] =
    client.statefulSets.namespace(namespaceName)

  override def watchApi(namespaceName: String)(implicit client: KubernetesClient[IO]): Watchable[IO, StatefulSet] =
    client.statefulSets.namespace(namespaceName)
} 
Example 28
Source File: EmbeddableLogHandler.scala    From tofu   with Apache License 2.0 5 votes vote down vote up
package tofu.doobie.log

import _root_.doobie.LogHandler
import cats.effect.IO
import cats.tagless.FunctorK
import cats.tagless.syntax.functorK._
import cats.{Applicative, FlatMap, Functor, ~>}
import tofu.higherKind.Embed
import tofu.lift.{Lift, UnliftIO}
import tofu.syntax.embed._
import tofu.syntax.monadic._


  def nop[F[_]: Applicative]: EmbeddableLogHandler[F] = new EmbeddableLogHandler(LogHandler.nop.pure[F])

  private def fromLogHandlerF[F[_]: Functor](
      logHandlerF: LogHandlerF[F]
  )(unsafeRunIO_ : IO[_] => Unit)(implicit U: UnliftIO[F]): EmbeddableLogHandler[F] =
    new EmbeddableLogHandler(U.unlift.map(toIO => LogHandler(event => unsafeRunIO_(toIO(logHandlerF.run(event))))))

  implicit val embeddableLogHandlerFunctorK: FunctorK[EmbeddableLogHandler] = new FunctorK[EmbeddableLogHandler] {
    def mapK[F[_], G[_]](af: EmbeddableLogHandler[F])(fk: F ~> G): EmbeddableLogHandler[G] =
      new EmbeddableLogHandler(fk(af.self))
  }
} 
Example 29
Source File: implicits.scala    From tofu   with Apache License 2.0 5 votes vote down vote up
package tofu.doobie.instances

import cats.effect.{Effect, IO, SyncEffect}
import doobie.ConnectionIO
import tofu.lift.Lift

object implicits extends DoobieImplicits1

private[instances] trait DoobieImplicits1 extends DoobieImplicits2 {
  @inline final implicit def liftToConnectionIOViaIOImplicit[F[_]: Lift[*[_], IO]]: LiftToConnectionIOViaIO[F] =
    liftToConnectionIOViaIO
}

private[instances] trait DoobieImplicits2 extends DoobieImplicitsScalaVersionSpecific {
  @inline final implicit def liftEffectToConnectionIOImplicit[F[_]: Effect]: LiftEffectToConnectionIO[F] =
    liftEffectToConnectionIO

  @inline final implicit def liftSyncEffectToConnectionIOImplicit[F[_]: SyncEffect]: LiftSyncEffectToConnectionIO[F] =
    liftSyncEffectToConnectionIO

  @inline final implicit def liftToConnectionRIOImplicit[F[_], R](implicit
      L: Lift[F, ConnectionIO]
  ): LiftToConnectionRIO[F, R] = liftToConnectionRIO
} 
Example 30
Source File: DoobieInstances.scala    From tofu   with Apache License 2.0 5 votes vote down vote up
package tofu.doobie.instances

import cats.data.ReaderT
import cats.effect.{Effect, IO, SyncEffect}
import cats.effect.syntax.effect._
import cats.effect.syntax.syncEffect._
import doobie.ConnectionIO
import doobie.free.connection.AsyncConnectionIO
import tofu.HasProvide
import tofu.doobie.ConnectionRIO
import tofu.lift.Lift

private[instances] trait DoobieInstances {
  final def liftToConnectionIOViaIO[F[_]: Lift[*[_], IO]]: LiftToConnectionIOViaIO[F] = new LiftToConnectionIOViaIO

  final def liftEffectToConnectionIO[F[_]: Effect]: LiftEffectToConnectionIO[F] = new LiftEffectToConnectionIO

  final def liftSyncEffectToConnectionIO[F[_]: SyncEffect]: LiftSyncEffectToConnectionIO[F] =
    new LiftSyncEffectToConnectionIO

  final def liftToConnectionRIO[F[_], R](implicit L: Lift[F, ConnectionIO]): LiftToConnectionRIO[F, R] =
    new LiftToConnectionRIO

  final def liftProvideToConnectionRIO[F[_], G[_], R](implicit
      HP: HasProvide[G, F, R],
      L: Lift[F, ConnectionIO]
  ): LiftProvideToConnectionRIO[F, G, R] = new LiftProvideToConnectionRIO
}

final class LiftToConnectionIOViaIO[F[_]](implicit L: Lift[F, IO]) extends Lift[F, ConnectionIO] {
  def lift[A](fa: F[A]): ConnectionIO[A] = AsyncConnectionIO.liftIO(L.lift(fa))
}

final class LiftEffectToConnectionIO[F[_]: Effect] extends Lift[F, ConnectionIO] {
  def lift[A](fa: F[A]): ConnectionIO[A] = AsyncConnectionIO.liftIO(fa.toIO)
}

final class LiftSyncEffectToConnectionIO[F[_]: SyncEffect] extends Lift[F, ConnectionIO] {
  def lift[A](fa: F[A]): ConnectionIO[A] = fa.runSync[ConnectionIO]
}

final class LiftToConnectionRIO[F[_], R](implicit L: Lift[F, ConnectionIO]) extends Lift[F, ConnectionRIO[R, *]] {
  def lift[A](fa: F[A]): ConnectionRIO[R, A] = ReaderT.liftF(L.lift(fa))
}

final class LiftProvideToConnectionRIO[F[_], G[_], R](implicit HP: HasProvide[G, F, R], L: Lift[F, ConnectionIO])
    extends Lift[G, ConnectionRIO[R, *]] {
  def lift[A](fa: G[A]): ConnectionRIO[R, A] = ReaderT(ctx => L.lift(HP.runContext(fa)(ctx)))
} 
Example 31
Source File: DoobieInstancesSuite.scala    From tofu   with Apache License 2.0 5 votes vote down vote up
package tofu.doobie

import cats.Applicative
import cats.data.ReaderT
import cats.effect.{IO, SyncIO}
import com.github.ghik.silencer.silent
import doobie.ConnectionIO
import monix.eval.{Coeval, Task}
import monix.execution.Scheduler
import tofu.doobie.instances.implicits._
import tofu.env.Env
import tofu.lift.Lift
import tofu.zioInstances.implicits._
import zio.interop.catz._

object DoobieInstancesSuite {

  def summonImplicitsViaLiftToIO[F[_]: Applicative, R](implicit L: Lift[F, IO]): Unit = {
    Lift[F, ConnectionIO]
    Lift[F, ConnectionRIO[R, *]]
    Lift[ReaderT[F, R, *], ConnectionRIO[R, *]]
    ()
  }

  def summonCatsEffectImplicits[R](): Unit = {
    Lift[SyncIO, ConnectionIO]
    Lift[SyncIO, ConnectionRIO[R, *]]
    Lift[ReaderT[SyncIO, R, *], ConnectionRIO[R, *]]

    Lift[IO, ConnectionIO]
    Lift[IO, ConnectionRIO[R, *]]
    Lift[ReaderT[IO, R, *], ConnectionRIO[R, *]]

    ()
  }

  def summonMonixImplicitsViaScheduler[R](implicit sc: Scheduler): Unit = {
    Lift[Coeval, ConnectionIO]
    Lift[Coeval, ConnectionRIO[R, *]]
    Lift[ReaderT[Coeval, R, *], ConnectionRIO[R, *]]

    Lift[Task, ConnectionIO]
    Lift[Task, ConnectionRIO[R, *]]
    Lift[ReaderT[Task, R, *], ConnectionRIO[R, *]]

    Lift[Env[R, *], ConnectionRIO[R, *]]

    ()
  }

  def summonMonixImplicitsUnambiguously[R](implicit @silent sc: Scheduler, L: Lift[Task, IO]): Unit = {
    Lift[Task, ConnectionIO]
    Lift[Task, ConnectionRIO[R, *]]
    Lift[ReaderT[Task, R, *], ConnectionRIO[R, *]]
    ()
  }

  def summonZioImplicits[R](): zio.Task[Unit] =
    zio.Task.concurrentEffect.map { implicit CE =>
      Lift[zio.Task, ConnectionIO]
      Lift[zio.Task, ConnectionRIO[R, *]]
      Lift[zio.RIO[R, *], ConnectionRIO[R, *]]
      ()
    }

  def summonLiftConnectionIO[R](): Unit = {
    LiftConnectionIO[ConnectionIO]
    LiftConnectionIO[ConnectionRIO[R, *]]
    ()
  }

} 
Example 32
Source File: EnvInstances.scala    From tofu   with Apache License 2.0 5 votes vote down vote up
package tofu.env

import cats.arrow.{ArrowChoice, FunctionK, Profunctor}
import cats.effect.IO
import cats.{Applicative, Monad, Parallel, ~>}
import monix.eval.{Task, TaskLift}
import monix.execution.Scheduler
import tofu.lift.{UnliftIO, UnsafeExecFuture}
import tofu.optics.Contains
import tofu.syntax.funk._

import scala.concurrent.Future

private[env] trait EnvInstances {
  self: Env.type =>

  private object anyEnvInstance extends EnvFunctorstance[Any]

  final implicit def envInstance[E]: EnvFunctorstance[E] = anyEnvInstance.asInstanceOf[EnvFunctorstance[E]]

  private object envParallelInstance extends Applicative[Env[Any, *]] {
    override def pure[A](x: A): Env[Any, A]                                                   = Env.pure(x)
    override def ap[A, B](ff: Env[Any, A => B])(fa: Env[Any, A]): Env[Any, B]                 =
      Env.parMap2(ff, fa)(_.apply(_))
    override def map2[A, B, Z](fa: Env[Any, A], fb: Env[Any, B])(f: (A, B) => Z): Env[Any, Z] =
      Env.parMap2(fa, fb)(f)
    override val unit: Env[Any, Unit]                                                         = Env.unit
    override def map[A, B](fa: Env[Any, A])(f: A => B): Env[Any, B]                           = fa.map(f)
    override def replicateA[A](n: Int, fa: Env[Any, A]): Env[Any, List[A]]                    =
      fa.mapTask(t => Task.parSequence(Iterable.fill(n)(t)).map(_.toList))
  }

  private object anyEnvParallelInstance extends Parallel[Env[Any, *]] {
    type F[a] = Env[Any, a]
    override def applicative: Applicative[Env[Any, *]]    = envParallelInstance
    override def monad: Monad[Env[Any, *]]                = anyEnvInstance
    override val sequential: ~>[Env[Any, *], Env[Any, *]] = FunctionK.id
    override val parallel: ~>[Env[Any, *], Env[Any, *]]   = FunctionK.id
  }

  final implicit def envParallelInstance[E]: Parallel[Env[E, *]] =
    anyEnvParallelInstance.asInstanceOf[Parallel[Env[E, *]]]

  final implicit val envProfuctorInstance: Profunctor[Env] with ArrowChoice[Env] =
    new Profunctor[Env] with ArrowChoice[Env] {
      override def choose[A, B, C, D](f: Env[A, C])(g: Env[B, D]): Env[Either[A, B], Either[C, D]] =
        Env {
          case Left(a)  => f.run(a).map(Left(_))
          case Right(b) => g.run(b).map(Right(_))
        }

      override def lift[A, B](f: A => B): Env[A, B]                                   = Env(a => Task.pure(f(a)))
      override def first[A, B, C](fa: Env[A, B]): Env[(A, C), (B, C)]                 =
        fa.first[C]
      override def second[A, B, C](fa: Env[A, B]): Env[(C, A), (C, B)]                =
        fa.second[C]
      override def compose[A, B, C](f: Env[B, C], g: Env[A, B]): Env[A, C]            =
        f.compose(g)
      override def rmap[A, B, C](fab: Env[A, B])(f: B => C): Env[A, C]                =
        fab.map(f)
      override def lmap[A, B, C](fab: Env[A, B])(f: C => A): Env[C, B]                =
        fab.localP(f)
      override def id[A]: Env[A, A]                                                   = Env.context
      override def dimap[A, B, C, D](fab: Env[A, B])(f: C => A)(g: B => D): Env[C, D] = fab.dimap(f)(g)
      override def split[A, B, C, D](f: Env[A, B], g: Env[C, D]): Env[(A, C), (B, D)] =
        f.split(g)
      override def left[A, B, C](fab: Env[A, B]): Env[Either[A, C], Either[B, C]]     = fab.left[C]
      override def right[A, B, C](fab: Env[A, B]): Env[Either[C, A], Either[C, B]]    = fab.right[C]
      override def choice[A, B, C](f: Env[A, C], g: Env[B, C]): Env[Either[A, B], C]  =
        f.choice(g)
      override def merge[A, B, C](f: Env[A, B], g: Env[A, C]): Env[A, (B, C)]         =
        Env.parZip2(f, g)
    }

  final implicit def envUnliftSubContext[E, E1: E Contains *]: EnvUnliftSubContext[E, E1] = new EnvUnliftSubContext

  def envUnsafeExecFuture[E](implicit sc: Scheduler): UnsafeExecFuture[Env[E, *]] =
    new UnsafeExecFuture[Env[E, *]] {
      def lift[A](fa: Future[A]): Env[E, A]   = Env.fromFuture(fa)
      def unlift: Env[E, Env[E, *] ~> Future] = Env.fromFunc(r => makeFunctionK(_.run(r).runToFuture))
    }

  implicit def envUnliftIO[E](implicit toIO: TaskLift[IO]): UnliftIO[Env[E, *]] = new UnliftIO[Env[E, *]] {
    def lift[A](fa: IO[A]): Env[E, A]   = Env.fromIO(fa)
    def unlift: Env[E, Env[E, *] ~> IO] = Env.fromFunc(r => funK(_.run(r).to[IO]))
  }
} 
Example 33
Source File: Timeout.scala    From tofu   with Apache License 2.0 5 votes vote down vote up
package tofu

import cats.effect.{Concurrent, ContextShift, IO, Timer}
import simulacrum.typeclass
import tofu.syntax.feither._
import tofu.internal.NonTofu

import scala.concurrent.duration.FiniteDuration

@typeclass
trait Timeout[F[_]] {
  def timeoutTo[A](fa: F[A], after: FiniteDuration, fallback: F[A]): F[A]
}

object Timeout extends LowPriorTimeoutImplicits {
  implicit def io(implicit timer: Timer[IO], cs: ContextShift[IO]): Timeout[IO] = new Timeout[IO] {
    override def timeoutTo[A](fa: IO[A], after: FiniteDuration, fallback: IO[A]): IO[A] = fa.timeoutTo(after, fallback)
  }
}

trait LowPriorTimeoutImplicits { self: Timeout.type =>
  implicit def concurrent[F[_]: NonTofu](implicit F: Concurrent[F], timer: Timer[F]): Timeout[F] =
    new Timeout[F] {
      override def timeoutTo[A](fa: F[A], after: FiniteDuration, fallback: F[A]): F[A] =
        F.race(timer.sleep(after), fa).getOrElseF(fallback)
    }
} 
Example 34
Source File: unlift.scala    From tofu   with Apache License 2.0 5 votes vote down vote up
package tofu.syntax

import cats.effect.{CancelToken, ConcurrentEffect, Effect, ExitCase, Fiber, IO, SyncIO}
import cats.{FlatMap, Functor, ~>}
import tofu.lift.Unlift

object unlift {

  implicit final class UnliftEffectOps[F[_], G[_]](private val U: Unlift[F, G]) extends AnyVal {
    def effect(implicit G: Functor[G], E: Effect[F]): G[Effect[G]] =
      G.map(U.unlift) { unliftF =>
        new EffectInstance[F, G] {
          def toG: F ~> G           = U.liftF
          def toF: G ~> F           = unliftF
          implicit def F: Effect[F] = E
        }
      }

    def effectWith[A](f: Effect[G] => G[A])(implicit G: FlatMap[G], E: Effect[F]): G[A] =
      G.flatMap(U.unlift) { unliftF =>
        val eff = new EffectInstance[F, G] {
          def toG: F ~> G           = U.liftF
          def toF: G ~> F           = unliftF
          implicit def F: Effect[F] = E
        }
        f(eff)
      }

    def concurrentEffect(implicit G: Functor[G], CE: ConcurrentEffect[F]): G[ConcurrentEffect[G]] =
      G.map(U.unlift) { unliftF =>
        new ConcurrentEffectInstance[F, G] {
          def toG: F ~> G                     = U.liftF
          def toF: G ~> F                     = unliftF
          implicit def F: ConcurrentEffect[F] = CE
        }
      }

    def concurrentEffectWith[A](f: ConcurrentEffect[G] => G[A])(implicit G: FlatMap[G], CE: ConcurrentEffect[F]): G[A] =
      G.flatMap(U.unlift) { unliftF =>
        val ce = new ConcurrentEffectInstance[F, G] {
          def toG: F ~> G                     = U.liftF
          def toF: G ~> F                     = unliftF
          implicit def F: ConcurrentEffect[F] = CE
        }
        f(ce)
      }

  }

  private[unlift] trait EffectInstance[F[_], G[_]] extends Effect[G] {
    def toG: F ~> G
    def toF: G ~> F
    implicit def F: Effect[F]

    def pure[A](x: A): G[A] = toG(F.pure(x))

    def flatMap[A, B](ga: G[A])(f: A => G[B]): G[B] = toG(F.flatMap(toF(ga))(a => toF(f(a))))

    def tailRecM[A, B](a: A)(f: A => G[Either[A, B]]): G[B] = toG(F.tailRecM(a)(a => toF(f(a))))

    def raiseError[A](e: Throwable): G[A] = toG(F.raiseError(e))

    def handleErrorWith[A](ga: G[A])(f: Throwable => G[A]): G[A] = toG(F.handleErrorWith(toF(ga))(t => toF(f(t))))

    def bracketCase[A, B](acquire: G[A])(use: A => G[B])(release: (A, ExitCase[Throwable]) => G[Unit]): G[B] =
      toG(F.bracketCase(toF(acquire))(a => toF(use(a)))((a, e) => toF(release(a, e))))

    def suspend[A](thunk: => G[A]): G[A] = toG(F.suspend(toF(thunk)))

    def async[A](k: (Either[Throwable, A] => Unit) => Unit): G[A] = toG(F.async(k))

    def asyncF[A](k: (Either[Throwable, A] => Unit) => G[Unit]): G[A] = toG(F.asyncF[A](cb => toF(k(cb))))

    def runAsync[A](ga: G[A])(cb: Either[Throwable, A] => IO[Unit]): SyncIO[Unit] = F.runAsync(toF(ga))(cb)
  }

  private[unlift] trait ConcurrentEffectInstance[F[_], G[_]] extends EffectInstance[F, G] with ConcurrentEffect[G] {
    implicit def F: ConcurrentEffect[F]

    def start[A](ga: G[A]): G[Fiber[G, A]] = toG(F.map(F.start(toF(ga)))(_.mapK(toG)))

    def racePair[A, B](ga: G[A], gb: G[B]): G[Either[(A, Fiber[G, B]), (Fiber[G, A], B)]] =
      toG(F.map(F.racePair(toF(ga), toF(gb))) {
        case Left((a, fb))  => Left((a, fb.mapK(toG)))
        case Right((fa, b)) => Right((fa.mapK(toG), b))
      })

    def runCancelable[A](ga: G[A])(cb: Either[Throwable, A] => IO[Unit]): SyncIO[CancelToken[G]] =
      F.runCancelable(toF(ga))(cb).map(toG(_))
  }

} 
Example 35
Source File: PrometheusMetricsReporterApiSpec.scala    From kafka4s   with Apache License 2.0 5 votes vote down vote up
package com.banno.kafka.metrics.prometheus

import scala.collection.compat._
import cats.implicits._
import cats.effect.IO
import com.banno.kafka._
import com.banno.kafka.producer._
import com.banno.kafka.consumer._
import org.apache.kafka.clients.producer.ProducerRecord
import org.apache.kafka.common.TopicPartition
import io.prometheus.client.CollectorRegistry
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.jdk.CollectionConverters._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class PrometheusMetricsReporterApiSpec extends AnyFlatSpec with Matchers with InMemoryKafka {
  implicit val defaultContextShift = IO.contextShift(ExecutionContext.global)
  implicit val defaultConcurrent = IO.ioConcurrentEffect(defaultContextShift)
  implicit val defaultTimer = IO.timer(ExecutionContext.global)

  //when kafka clients change their metrics, this test will help identify the changes we need to make
  "Prometheus reporter" should "register Prometheus collectors for all known Kafka metrics" in {
    val topic = createTopic(2)
    val records =
      List(new ProducerRecord(topic, 0, "a", "a"), new ProducerRecord(topic, 1, "b", "b"))
    ProducerApi
      .resource[IO, String, String](
        BootstrapServers(bootstrapServer),
        MetricReporters[ProducerPrometheusReporter]
      )
      .use(
        p =>
          ConsumerApi
            .resource[IO, String, String](
              BootstrapServers(bootstrapServer),
              ClientId("c1"),
              MetricReporters[ConsumerPrometheusReporter]
            )
            .use(
              c1 =>
                ConsumerApi
                  .resource[IO, String, String](
                    BootstrapServers(bootstrapServer),
                    ClientId("c2"),
                    MetricReporters[ConsumerPrometheusReporter]
                  )
                  .use(
                    c2 =>
                      for {
                        _ <- p.sendSyncBatch(records)

                        _ <- c1.assign(topic, Map.empty[TopicPartition, Long])
                        _ <- c1.poll(1 second)
                        _ <- c1.poll(1 second)

                        _ <- c2.assign(topic, Map.empty[TopicPartition, Long])
                        _ <- c2.poll(1 second)
                        _ <- c2.poll(1 second)

                        _ <- IO.sleep(PrometheusMetricsReporterApi.defaultUpdatePeriod + (1 second))
                        _ <- p.close
                        _ <- c1.close
                        _ <- c2.close
                      } yield {
                        val registry = CollectorRegistry.defaultRegistry
                        registry.metricFamilySamples.asScala
                          .count(_.name.startsWith("kafka_producer")) should ===(56)
                        registry.metricFamilySamples.asScala
                          .find(_.name == "kafka_producer_record_send_total")
                          .map(_.samples.asScala.map(_.value)) should ===(Some(List(2)))

                        registry.metricFamilySamples.asScala
                          .count(_.name.startsWith("kafka_consumer")) should ===(50)
                        registry.metricFamilySamples.asScala
                          .find(_.name == "kafka_consumer_records_consumed_total")
                          .map(_.samples.asScala.map(_.value)) should ===(Some(List(2, 2)))
                        registry.metricFamilySamples.asScala
                          .find(_.name == "kafka_consumer_topic_records_consumed_total")
                          .map(_.samples.asScala.map(_.value)) should ===(Some(List(2, 2)))
                      }
                  )
            )
      )
      .unsafeRunSync()
  }

} 
Example 36
Source File: InMemoryKafka.scala    From kafka4s   with Apache License 2.0 5 votes vote down vote up
package com.banno.kafka

import org.scalatest.{BeforeAndAfterAll, Suite}
import org.scalacheck.Gen
import com.banno.kafka.admin.AdminApi
import cats.effect.IO
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import org.apache.kafka.clients.admin.NewTopic

trait InMemoryKafka extends BeforeAndAfterAll { this: Suite =>

  val log = Slf4jLogger.getLoggerFromClass[IO](this.getClass)

  val bootstrapServer = "localhost:9092"
  // val bootstrapServer = "kafka.local:9092"
  val schemaRegistryUrl = "http://localhost:8081"
  // val schemaRegistryUrl = "http://kafka.local:8081"

  override def beforeAll(): Unit =
    log.info(s"Using docker-machine Kafka cluster for ${getClass.getName}").unsafeRunSync()

  override def afterAll(): Unit =
    log.info(s"Used docker-machine Kafka cluster for ${getClass.getName}").unsafeRunSync()

  def randomId: String = Gen.listOfN(10, Gen.alphaChar).map(_.mkString).sample.get
  def genGroupId: String = randomId
  def genTopic: String = randomId
  def createTopic(partitionCount: Int = 1): String = {
    val topic = genTopic
    AdminApi
      .createTopicsIdempotent[IO](
        bootstrapServer,
        List(new NewTopic(topic, partitionCount, 1.toShort))
      )
      .unsafeRunSync()
    topic
  }

} 
Example 37
Source File: ServerApp.scala    From fs2-chat   with MIT License 5 votes vote down vote up
package fs2chat
package server

import cats.effect.{Blocker, ExitCode, IO, IOApp}
import cats.implicits._
import com.comcast.ip4s._
import com.monovore.decline._
import fs2.io.tcp.SocketGroup
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger

object ServerApp extends IOApp {
  private val argsParser: Command[Port] =
    Command("fs2chat-server", "FS2 Chat Server") {
      Opts
        .option[Int]("port", "Port to bind for connection requests")
        .withDefault(5555)
        .mapValidated(p => Port(p).toValidNel("Invalid port number"))
    }

  def run(args: List[String]): IO[ExitCode] =
    argsParser.parse(args) match {
      case Left(help) => IO(System.err.println(help)).as(ExitCode.Error)
      case Right(port) =>
        Blocker[IO]
          .use { blocker =>
            SocketGroup[IO](blocker).use { socketGroup =>
              Slf4jLogger.create[IO].flatMap { implicit logger =>
                Server.start[IO](socketGroup, port).compile.drain
              }
            }
          }
          .as(ExitCode.Success)
    }
} 
Example 38
Source File: ProfileCtrl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.pages.user.profile

import cats.effect.IO
import com.mohiva.play.silhouette.api.Silhouette
import gospeak.core.domain.User
import gospeak.core.services.storage.{UserGroupRepo, UserProposalRepo, UserTalkRepo, UserUserRepo}
import gospeak.web.AppConf
import gospeak.web.auth.domain.CookieEnv
import gospeak.web.domain.Breadcrumb
import gospeak.web.pages.published.speakers.routes.{SpeakerCtrl => PublishedSpeakerRoutes}
import gospeak.web.pages.user.UserCtrl
import gospeak.web.pages.user.profile.ProfileCtrl._
import gospeak.web.utils.{GsForms, UICtrl, UserReq}
import play.api.data.Form
import play.api.mvc.{Action, AnyContent, ControllerComponents, Result}

class ProfileCtrl(cc: ControllerComponents,
                  silhouette: Silhouette[CookieEnv],
                  conf: AppConf,
                  groupRepo: UserGroupRepo,
                  proposalRepo: UserProposalRepo,
                  talkRepo: UserTalkRepo,
                  userRepo: UserUserRepo) extends UICtrl(cc, silhouette, conf) {
  def edit(): Action[AnyContent] = UserAction { implicit req =>
    editView(GsForms.user)
  }

  def doEdit(): Action[AnyContent] = UserAction { implicit req =>
    GsForms.user.bindFromRequest.fold(
      formWithErrors => editView(formWithErrors),
      data => userRepo.edit(data)
        .map(_ => Redirect(PublishedSpeakerRoutes.detail(req.user.slug)).flashing("success" -> "Profile updated"))
    )
  }

  private def editView(form: Form[User.Data])(implicit req: UserReq[AnyContent]): IO[Result] = {
    val filledForm = if (form.hasErrors) form else form.fill(req.user.data)
    IO(Ok(html.edit(filledForm)(editBreadcrumb)))
  }
}

object ProfileCtrl {
  def breadcrumb(implicit req: UserReq[AnyContent]): Breadcrumb =
    UserCtrl.breadcrumb.add("Profile" -> PublishedSpeakerRoutes.detail(req.user.slug))

  def editBreadcrumb(implicit req: UserReq[AnyContent]): Breadcrumb =
    breadcrumb.add("Edit" -> routes.ProfileCtrl.edit())
} 
Example 39
Source File: HomeCtrl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.pages.published

import cats.effect.IO
import com.mohiva.play.silhouette.api.Silhouette
import gospeak.core.services.storage._
import gospeak.web.AppConf
import gospeak.web.auth.domain.CookieEnv
import gospeak.web.domain.Breadcrumb
import gospeak.web.pages.published.HomeCtrl._
import gospeak.web.utils.UICtrl
import play.api.mvc._

class HomeCtrl(cc: ControllerComponents,
               silhouette: Silhouette[CookieEnv],
               conf: AppConf,
               userRepo: PublicUserRepo,
               talkRepo: PublicTalkRepo,
               groupRepo: PublicGroupRepo,
               cfpRepo: PublicCfpRepo,
               eventRepo: PublicEventRepo,
               proposalRepo: PublicProposalRepo,
               externalCfpRepo: PublicExternalCfpRepo,
               externalEvent: PublicExternalEventRepo,
               externalProposal: PublicExternalProposalRepo) extends UICtrl(cc, silhouette, conf) {
  def index(): Action[AnyContent] = UserAwareAction { implicit req =>
    IO.pure(Ok(html.index()(breadcrumb())))
  }

  def why(): Action[AnyContent] = UserAwareAction { implicit req =>
    IO.pure(Ok(html.why()(breadcrumb().add("Why use Gospeak" -> routes.HomeCtrl.why()))))
  }

  def videoNewsletter(): Action[AnyContent] = UserAwareAction { implicit req =>
    IO.pure(Ok(html.videoNewsletter()(breadcrumb().add("Video newsletter" -> routes.HomeCtrl.videoNewsletter()))))
  }

  def sitemap(): Action[AnyContent] = UserAwareAction { implicit req =>
    for {
      users <- userRepo.listAllPublicSlugs().map(_.toMap)
      talks <- talkRepo.listAllPublicSlugs()
      groups <- groupRepo.listAllSlugs().map(_.toMap)
      events <- eventRepo.listAllPublishedSlugs()
      proposals <- proposalRepo.listAllPublicIds()
      cfps <- cfpRepo.listAllPublicSlugs()
      extCfps <- externalCfpRepo.listAllIds()
      extEvents <- externalEvent.listAllIds()
      extProposals <- externalProposal.listAllPublicIds()
    } yield Ok(html.sitemap(users, talks, groups, events, proposals, cfps, extCfps, extEvents, extProposals)).as("text/xml")
  }
}

object HomeCtrl {
  def breadcrumb(): Breadcrumb =
    Breadcrumb("Home", routes.HomeCtrl.index())
} 
Example 40
Source File: SpeakerCtrl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.pages.published.speakers

import cats.data.OptionT
import cats.effect.IO
import com.mohiva.play.silhouette.api.Silhouette
import gospeak.core.domain.{Proposal, Talk, User}
import gospeak.core.services.email.EmailSrv
import gospeak.core.services.storage._
import gospeak.libs.scala.domain.Page
import gospeak.web.AppConf
import gospeak.web.auth.domain.CookieEnv
import gospeak.web.domain.Breadcrumb
import gospeak.web.emails.Emails
import gospeak.web.pages.published.HomeCtrl
import gospeak.web.pages.published.speakers.SpeakerCtrl._
import gospeak.web.utils._
import play.api.mvc._

class SpeakerCtrl(cc: ControllerComponents,
                  silhouette: Silhouette[CookieEnv],
                  conf: AppConf,
                  userRepo: PublicUserRepo,
                  talkRepo: PublicTalkRepo,
                  externalProposalRepo: PublicExternalProposalRepo,
                  groupRepo: PublicGroupRepo,
                  emailSrv: EmailSrv) extends UICtrl(cc, silhouette, conf) {
  def list(params: Page.Params): Action[AnyContent] = UserAwareAction { implicit req =>
    userRepo.listPublic(params).map(speakers => Ok(html.list(speakers)(listBreadcrumb())))
  }

  def detail(user: User.Slug): Action[AnyContent] = UserAwareAction { implicit req =>
    (for {
      speakerElt <- OptionT(userRepo.findPublic(user))
      groups <- OptionT.liftF(groupRepo.listFull(speakerElt.id))
      talks <- OptionT.liftF(talkRepo.listAll(speakerElt.id, Talk.Status.Public))
      proposals <- OptionT.liftF(externalProposalRepo.listAllCommon(speakerElt.id, Proposal.Status.Accepted))
      users <- OptionT.liftF(userRepo.list((groups.flatMap(_.owners.toList) ++ talks.flatMap(_.users)).distinct))
      res = Ok(html.detail(speakerElt, groups, talks, proposals.groupBy(_.talk.id), users)(breadcrumb(speakerElt)))
    } yield res).value.map(_.getOrElse(publicUserNotFound(user)))
  }

  def talk(user: User.Slug, talk: Talk.Slug): Action[AnyContent] = UserAwareAction { implicit req =>
    (for {
      userElt <- OptionT(userRepo.findPublic(user))
      talkElt <- OptionT(talkRepo.findPublic(talk, userElt.id))
      proposals <- OptionT.liftF(externalProposalRepo.listAllCommon(talkElt.id, Proposal.Status.Accepted))
      users <- OptionT.liftF(userRepo.list((talkElt.users ++ proposals.flatMap(_.users)).distinct))
      res = Ok(html.talk(userElt, talkElt, proposals, users)(breadcrumb(userElt, talkElt)))
    } yield res).value.map(_.getOrElse(publicTalkNotFound(user, talk)))
  }

  def contactSpeaker(user: User.Slug): Action[AnyContent] = UserAction { implicit req =>
    val next = Redirect(routes.SpeakerCtrl.detail(user))
    GsForms.speakerContact.bindFromRequest.fold(
      formWithErrors => IO.pure(next.flashing(formWithErrors.flash)),
      data => (for {
        speakerElt <- OptionT(userRepo.findPublic(user)(req.userAware))
        _ <- OptionT.liftF(emailSrv.send(Emails.contactSpeaker(data.subject, data.content, speakerElt.user)))
        res = next.flashing("success" -> "The message has been sent!")
      } yield res).value.map(_.getOrElse(publicUserNotFound(user))))
  }
}

object SpeakerCtrl {
  def listBreadcrumb(): Breadcrumb =
    HomeCtrl.breadcrumb().add("Speakers" -> routes.SpeakerCtrl.list())

  def breadcrumb(speaker: User.Full): Breadcrumb =
    listBreadcrumb().add(speaker.name.value -> routes.SpeakerCtrl.detail(speaker.slug))

  def breadcrumb(speaker: User.Full, talk: Talk): Breadcrumb =
    breadcrumb(speaker).add("Talks" -> routes.SpeakerCtrl.detail(speaker.slug)).add(talk.title.value -> routes.SpeakerCtrl.talk(speaker.slug, talk.slug))
} 
Example 41
Source File: CfpCtrl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.pages.orga.cfps

import cats.data.OptionT
import cats.effect.IO
import com.mohiva.play.silhouette.api.Silhouette
import gospeak.core.domain.utils.OrgaCtx
import gospeak.core.domain.{Cfp, Event, Group}
import gospeak.core.services.storage._
import gospeak.web.AppConf
import gospeak.web.auth.domain.CookieEnv
import gospeak.web.domain.Breadcrumb
import gospeak.web.pages.orga.GroupCtrl
import gospeak.web.pages.orga.cfps.CfpCtrl._
import gospeak.web.pages.orga.events.routes.{EventCtrl => EventRoutes}
import gospeak.web.utils.{GsForms, OrgaReq, UICtrl}
import gospeak.libs.scala.domain.Page
import play.api.data.Form
import play.api.mvc.{Action, AnyContent, ControllerComponents, Result}

class CfpCtrl(cc: ControllerComponents,
              silhouette: Silhouette[CookieEnv],
              conf: AppConf,
              userRepo: OrgaUserRepo,
              val groupRepo: OrgaGroupRepo,
              cfpRepo: OrgaCfpRepo,
              eventRepo: OrgaEventRepo,
              proposalRepo: OrgaProposalRepo) extends UICtrl(cc, silhouette, conf) with UICtrl.OrgaAction {
  def list(group: Group.Slug, params: Page.Params): Action[AnyContent] = OrgaAction(group) { implicit req =>
    val customParams = params.withNullsFirst
    cfpRepo.list(customParams).map(cfps => Ok(html.list(cfps)(listBreadcrumb))) // TODO listWithProposalCount
  }

  def create(group: Group.Slug, event: Option[Event.Slug]): Action[AnyContent] = OrgaAction(group) { implicit req =>
    createView(group, GsForms.cfp, event)
  }

  def doCreate(group: Group.Slug, event: Option[Event.Slug]): Action[AnyContent] = OrgaAction(group) { implicit req =>
    GsForms.cfp.bindFromRequest.fold(
      formWithErrors => createView(group, formWithErrors, event),
      data => (for {
        // TODO check if slug not already exist
        cfpElt <- OptionT.liftF(cfpRepo.create(data))
        redirect <- OptionT.liftF(event.map { e =>
          eventRepo.attachCfp(e, cfpElt.id)
            .map(_ => Redirect(EventRoutes.detail(group, e)))
          // TODO recover and redirect to cfp detail
        }.getOrElse {
          IO.pure(Redirect(routes.CfpCtrl.detail(group, data.slug)))
        })
      } yield redirect).value.map(_.getOrElse(groupNotFound(group)))
    )
  }

  private def createView(group: Group.Slug, form: Form[Cfp.Data], event: Option[Event.Slug])(implicit req: OrgaReq[AnyContent]): IO[Result] = {
    val b = listBreadcrumb.add("New" -> routes.CfpCtrl.create(group))
    IO.pure(Ok(html.create(form, event)(b)))
  }

  def detail(group: Group.Slug, cfp: Cfp.Slug, params: Page.Params): Action[AnyContent] = OrgaAction(group) { implicit req =>
    (for {
      cfpElt <- OptionT(cfpRepo.find(cfp))
      proposals <- OptionT.liftF(proposalRepo.listFull(cfp, params))
      speakers <- OptionT.liftF(userRepo.list(proposals.items.flatMap(_.users).distinct))
      userRatings <- OptionT.liftF(proposalRepo.listRatings(cfp))
      b = breadcrumb(cfpElt)
    } yield Ok(html.detail(cfpElt, proposals, speakers, userRatings)(b))).value.map(_.getOrElse(cfpNotFound(group, cfp)))
  }

  def edit(group: Group.Slug, cfp: Cfp.Slug, redirect: Option[String]): Action[AnyContent] = OrgaAction(group) { implicit req =>
    editView(group, cfp, GsForms.cfp, redirect)
  }

  def doEdit(group: Group.Slug, cfp: Cfp.Slug, redirect: Option[String]): Action[AnyContent] = OrgaAction(group) { implicit req =>
    GsForms.cfp.bindFromRequest.fold(
      formWithErrors => editView(group, cfp, formWithErrors, redirect),
      data => (for {
        cfpOpt <- OptionT.liftF(cfpRepo.find(data.slug))
        res <- OptionT.liftF(cfpOpt match {
          case Some(duplicate) if data.slug != cfp => editView(group, cfp, GsForms.cfp.fillAndValidate(data).withError("slug", s"Slug already taken by cfp: ${duplicate.name.value}"), redirect)
          case _ => cfpRepo.edit(cfp, data).map { _ => redirectOr(redirect, routes.CfpCtrl.detail(group, data.slug)) }
        })
      } yield res).value.map(_.getOrElse(groupNotFound(group)))
    )
  }

  private def editView(group: Group.Slug, cfp: Cfp.Slug, form: Form[Cfp.Data], redirect: Option[String])(implicit req: OrgaReq[AnyContent], ctx: OrgaCtx): IO[Result] = {
    (for {
      cfpElt <- OptionT(cfpRepo.find(cfp))
      filledForm = if (form.hasErrors) form else form.fill(cfpElt.data)
      b = breadcrumb(cfpElt).add("Edit" -> routes.CfpCtrl.edit(group, cfp))
    } yield Ok(html.edit(cfpElt, filledForm, redirect)(b))).value.map(_.getOrElse(cfpNotFound(group, cfp)))
  }
}

object CfpCtrl {
  def listBreadcrumb(implicit req: OrgaReq[AnyContent]): Breadcrumb =
    GroupCtrl.breadcrumb.add("CFPs" -> routes.CfpCtrl.list(req.group.slug))

  def breadcrumb(cfp: Cfp)(implicit req: OrgaReq[AnyContent]): Breadcrumb =
    listBreadcrumb.add(cfp.name.value -> routes.CfpCtrl.detail(req.group.slug, cfp.slug))
} 
Example 42
Source File: SwaggerCtrl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.api.swagger

import cats.effect.IO
import com.mohiva.play.silhouette.api.Silhouette
import gospeak.web.AppConf
import gospeak.web.auth.domain.CookieEnv
import gospeak.web.utils.{OpenApiUtils, UICtrl}
import play.api.mvc.{Action, AnyContent, ControllerComponents}

class SwaggerCtrl(cc: ControllerComponents,
                  silhouette: Silhouette[CookieEnv],
                  conf: AppConf) extends UICtrl(cc, silhouette, conf) {
  private val spec = OpenApiUtils.loadSpec().get // to fail app on start

  def getSpec: Action[AnyContent] = UserAwareAction { implicit req =>
    IO.pure(Ok(spec))
  }

  def getUi: Action[AnyContent] = UserAwareAction { implicit req =>
    IO.pure(Ok(html.swaggerUi()))
  }
} 
Example 43
Source File: UtilsCtrl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.api.ui

import cats.effect.IO
import com.mohiva.play.silhouette.api.Silhouette
import gospeak.core.domain.ExternalCfp
import gospeak.core.domain.messages.Message
import gospeak.core.services.cloudinary.CloudinarySrv
import gospeak.core.services.slack.SlackSrv
import gospeak.core.services.slack.domain.SlackToken
import gospeak.core.services.storage.PublicExternalCfpRepo
import gospeak.infra.services.EmbedSrv
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.{Html, Markdown, Mustache, Url}
import gospeak.web.AppConf
import gospeak.web.api.domain.ApiExternalCfp
import gospeak.web.api.domain.utils.ApiResult
import gospeak.web.api.ui.helpers.JsonFormats._
import gospeak.web.auth.domain.CookieEnv
import gospeak.web.services.MessageSrv
import gospeak.web.utils.ApiCtrl
import play.api.libs.json._
import play.api.mvc._
import play.twirl.api.HtmlFormat

import scala.util.control.NonFatal

case class ValidationResult(valid: Boolean, message: String)

case class TemplateDataResponse(data: JsValue)

case class TemplateRequest(template: Mustache.Markdown[Any], ref: Option[Message.Ref], markdown: Boolean)

case class TemplateResponse(result: Option[Html], error: Option[String])

class UtilsCtrl(cc: ControllerComponents,
                silhouette: Silhouette[CookieEnv],
                conf: AppConf,
                externalCfpRepo: PublicExternalCfpRepo,
                cloudinarySrv: CloudinarySrv,
                slackSrv: SlackSrv,
                ms: MessageSrv) extends ApiCtrl(cc, silhouette, conf) {
  def cloudinarySignature(): Action[AnyContent] = UserAction[String] { implicit req =>
    val queryParams = req.queryString.flatMap { case (key, values) => values.headOption.map(value => (key, value)) }
    IO.pure(cloudinarySrv.signRequest(queryParams) match {
      case Right(signature) => ApiResult.of(signature)
      case Left(error) => ApiResult.badRequest(error)
    })
  }

  def validateSlackToken(token: String): Action[AnyContent] = UserAction[ValidationResult] { implicit req =>
    SlackToken.from(token, conf.app.aesKey).toIO.flatMap(slackSrv.getInfos(_, conf.app.aesKey))
      .map(infos => ValidationResult(valid = true, s"Token for ${infos.teamName} team, created by ${infos.userName}"))
      .recover { case NonFatal(e) => ValidationResult(valid = false, s"Invalid token: ${e.getMessage}") }
      .map(ApiResult.of(_))
  }

  def duplicatesExtCfp(params: ExternalCfp.DuplicateParams): Action[AnyContent] = UserAction[Seq[ApiExternalCfp.Published]] { implicit req =>
    externalCfpRepo.listDuplicatesFull(params).map(cfps => ApiResult.of(cfps.map(ApiExternalCfp.published)))
  }

  def embed(url: Url): Action[AnyContent] = UserAwareAction { implicit req =>
    EmbedSrv.embedCode(url).map(_.value).map(ApiResult.of(_))
  }

  def markdownToHtml(): Action[JsValue] = UserAwareActionJson[String, String] { implicit req =>
    val html = Markdown(req.body).toHtml
    IO.pure(ApiResult.of(html.value))
  }

  def templateData(ref: Message.Ref): Action[AnyContent] = UserAction[TemplateDataResponse] { implicit req =>
    val data = circeToPlay(ms.sample(Some(ref)))
    IO.pure(ApiResult.of(TemplateDataResponse(data)))
  }

  def renderTemplate(): Action[JsValue] = UserActionJson[TemplateRequest, TemplateResponse] { implicit req =>
    val data = ms.sample(req.body.ref)(req.withBody(AnyContent()))
    val tmpl = req.body.template.render(data)
    val res = tmpl match {
      case Left(err) => TemplateResponse(None, Some(err.message))
      case Right(tmpl) if req.body.markdown => TemplateResponse(Some(tmpl.toHtml), None)
      case Right(tmpl) => TemplateResponse(Some(Html(s"<pre>${HtmlFormat.escape(tmpl.value)}</pre>")), None)
    }
    IO.pure(ApiResult.of(res))
  }

  private def circeToPlay(json: io.circe.Json): JsValue = {
    json.fold(
      jsonNull = JsNull,
      jsonBoolean = (b: Boolean) => JsBoolean(b),
      jsonNumber = (n: io.circe.JsonNumber) => JsNumber(n.toBigDecimal.getOrElse(BigDecimal(n.toDouble))),
      jsonString = (s: String) => JsString(s),
      jsonArray = (v: Vector[io.circe.Json]) => JsArray(v.map(circeToPlay)),
      jsonObject = (o: io.circe.JsonObject) => JsObject(o.toMap.mapValues(circeToPlay))
    )
  }
} 
Example 44
Source File: SchedulerSrv.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.services

import java.time.Instant

import cats.effect.{IO, Timer}
import cron4s.CronExpr
import eu.timepit.fs2cron.awakeEveryCron
import fs2.Stream
import gospeak.core.domain.utils.{AdminCtx, Constants}
import gospeak.core.services.storage.AdminVideoRepo
import gospeak.core.services.twitter.TwitterSrv
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.TimeUtils
import gospeak.web.services.SchedulerSrv.{Conf, Exec, Scheduler}

import scala.collection.mutable
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration
import scala.util.control.NonFatal

class SchedulerSrv(videoRepo: AdminVideoRepo,
                   twitterSrv: TwitterSrv)(implicit ec: ExecutionContext) {
  implicit private val timer: Timer[IO] = IO.timer(ec)
  private val schedulers = mutable.ListBuffer[Scheduler]()
  private val execs: mutable.ListBuffer[Exec] = mutable.ListBuffer[Exec]()

  def getSchedulers: List[Scheduler] = schedulers.toList

  def getExecs: List[Exec] = execs.toList

  def init(conf: Conf): Unit = {
    schedule("tweet random video", conf.tweetRandomVideo, tweetRandomVideo())
  }

  def exec(name: String)(implicit ctx: AdminCtx): IO[Option[Exec]] =
    schedulers.find(_.name == name).map(exec(_, s"manual (${ctx.user.name.value})")).sequence

  private def tweetRandomVideo(): IO[(String, Option[String])] = for {
    video <- videoRepo.findRandom()
    tweet <- (for {
      v <- video.toRight("No video available")
    } yield twitterSrv.tweet(s"#OneDayOneTalk [${v.lang}] ${v.title} on ${v.channel.name} in ${v.publishedAt.getYear(Constants.defaultZoneId)} ${v.url.value}")).sequence
  } yield (tweet.map(t => s"Tweet sent: ${t.text}").getOrElse("Tweet not sent"), tweet.swap.toOption)

  // TODO be able to stop/start a scheduler
  private def schedule(name: String, cron: CronExpr, task: IO[(String, Option[String])]): Unit = {
    schedulers.find(_.name == name).map(_ => ()).getOrElse {
      val scheduler = Scheduler(name, cron, Some(Instant.now()), task)
      schedulers += scheduler
      val stream = awakeEveryCron[IO](cron).flatMap { _ => Stream.eval(exec(scheduler, "auto")) }
      stream.compile.drain.unsafeRunAsyncAndForget
    }
  }

  private def exec(s: Scheduler, source: String): IO[Exec] = IO(Instant.now()).flatMap { start =>
    s.task.map {
      case (res, None) => Exec(s.name, source, start, Instant.now(), res, None)
      case (res, Some(err)) => Exec(s.name, source, start, Instant.now(), res, Some(err))
    }.recover {
      case NonFatal(e) => Exec(s.name, source, start, Instant.now(), s"Finished with ${e.getClass.getSimpleName}", Some(e.getMessage))
    }.map { e =>
      execs += e
      e
    }
  }
}

object SchedulerSrv {

  final case class Conf(tweetRandomVideo: CronExpr)

  final case class Scheduler(name: String,
                             schedule: CronExpr,
                             started: Option[Instant],
                             private[SchedulerSrv] val task: IO[(String, Option[String])])

  final case class Exec(name: String,
                        source: String,
                        started: Instant,
                        finished: Instant,
                        result: String,
                        error: Option[String]) {
    def duration: FiniteDuration = TimeUtils.toFiniteDuration(started, finished)
  }

} 
Example 45
Source File: MessageHandler.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.web.services

import java.time.LocalDateTime

import cats.data.OptionT
import cats.effect.IO
import gospeak.core.ApplicationConf
import gospeak.core.domain.Group
import gospeak.core.domain.Group.Settings.Action
import gospeak.core.domain.Group.Settings.Action.Trigger
import gospeak.core.domain.messages.Message
import gospeak.core.domain.utils.Constants
import gospeak.core.services.email.EmailSrv
import gospeak.core.services.slack.SlackSrv
import gospeak.core.services.storage.{OrgaGroupRepo, OrgaGroupSettingsRepo}
import gospeak.core.services.twitter.{Tweets, TwitterSrv}
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.{CustomException, EmailAddress}
import gospeak.web.services.MessageSrv._
import io.circe.Json
import org.slf4j.LoggerFactory

import scala.util.control.NonFatal

class MessageHandler(appConf: ApplicationConf,
                     groupRepo: OrgaGroupRepo,
                     groupSettingsRepo: OrgaGroupSettingsRepo,
                     emailSrv: EmailSrv,
                     slackSrv: SlackSrv,
                     twitterSrv: TwitterSrv) {
  private val logger = LoggerFactory.getLogger(this.getClass)

  def groupActionHandler(msg: Message): IO[Unit] = (msg match {
    case m: Message.GroupMessage => handleGroupAction(m.group.slug, m, eMessage(m))
    case _ => IO.pure(0)
  }).map(_ => ()).recover { case NonFatal(_) => () }

  def gospeakHandler(msg: Message): IO[Unit] = (msg match {
    case m: Message.ExternalCfpCreated => gospeakTwitt(m)
    case _ => IO.pure(0)
  }).map(_ => ()).recover { case NonFatal(_) => () }

  def logHandler(msg: Message): IO[Unit] = IO.pure(logger.info(s"Message sent: $msg"))

  private def handleGroupAction(group: Group.Slug, msg: Message.GroupMessage, data: Json): IO[Int] = (for {
    groupElt <- OptionT(groupRepo.find(group))
    actions <- OptionT.liftF(groupSettingsRepo.findActions(groupElt.id))
    accounts <- OptionT.liftF(groupSettingsRepo.findAccounts(groupElt.id))
    actionsToExec = Trigger.all.filter(_.message == msg.ref).flatMap(actions.getOrElse(_, Seq()))
    results <- OptionT.liftF(actionsToExec.map(execGroupAction(accounts, _, data)).sequence)
  } yield results.length).value.map(_.getOrElse(0))

  private def execGroupAction(accounts: Group.Settings.Accounts, action: Action, data: Json): IO[Unit] = action match {
    case email: Action.Email =>
      (for {
        to <- email.to.render(data).left.map(e => CustomException(e.message)).flatMap(EmailAddress.from).map(EmailAddress.Contact(_))
        subject <- email.subject.render(data).left.map(e => CustomException(e.message))
        content <- email.content.render(data).map(_.toHtml).leftMap(e => CustomException(e.message))
      } yield emailSrv.send(EmailSrv.Email(
        from = Constants.Gospeak.noreplyEmail,
        to = Seq(to),
        subject = subject,
        content = EmailSrv.HtmlContent(content.value)
      ))).toIO.flatMap(identity).map(_ => ())
    case Action.Slack(slack) =>
      accounts.slack.map(slackSrv.exec(slack, data, _, appConf.aesKey)).getOrElse(IO.raiseError(CustomException("No credentials for Slack")))
  }

  private def gospeakTwitt(msg: Message.ExternalCfpCreated): IO[Int] = {
    if (msg.cfp.isActive(LocalDateTime.now())) {
      twitterSrv.tweet(Tweets.externalCfpCreated(msg)).map(_ => 1)
    } else {
      IO.pure(0)
    }
  }
} 
Example 46
Source File: SendGridEmailSrv.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.email

import cats.effect.IO
import gospeak.core.services.email.EmailSrv.{Email, HtmlContent, TextContent}
import gospeak.core.services.email.{EmailConf, EmailSrv}
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.Done
import gospeak.libs.scala.domain.EmailAddress.Contact

class SendGridEmailSrv private(client: com.sendgrid.SendGrid) extends EmailSrv {

  import com.sendgrid.helpers.mail.{objects => sgO}
  import com.sendgrid.helpers.{mail => sgM}
  import com.{sendgrid => sg}

  override def send(email: Email): IO[Done] = {
    // https://github.com/sendgrid/sendgrid-java/blob/master/examples/helpers/mail/Example.java#L30
    val mail = buildMail(email)
    val request = new sg.Request()
    request.setMethod(sg.Method.POST)
    request.setEndpoint("mail/send")
    for {
      body <- IO(mail.build)
      _ = request.setBody(body)
      _ <- IO(client.api(request)).filter(_.getStatusCode == 202)
    } yield Done
  }

  private def buildMail(email: Email): sgM.Mail = {
    val mail = new sgM.Mail()
    mail.setFrom(buildEmail(email.from))
    val personalization = new sgO.Personalization()
    email.to.foreach(to => personalization.addTo(buildEmail(to)))
    mail.addPersonalization(personalization)
    mail.setSubject(email.subject)
    email.content match {
      case TextContent(text) => mail.addContent(new sgO.Content("text/plain", text))
      case HtmlContent(html) => mail.addContent(new sgO.Content("text/html", html))
    }
    mail
  }

  private def buildEmail(contact: Contact): sgO.Email =
    contact.name.map { name =>
      new sgO.Email(contact.address.value, name)
    }.getOrElse {
      new sgO.Email(contact.address.value)
    }
}

object SendGridEmailSrv {
  def apply(conf: EmailConf.SendGrid): SendGridEmailSrv =
    new SendGridEmailSrv(new com.sendgrid.SendGrid(conf.apiKey.decode))
} 
Example 47
Source File: ConsoleEmailSrv.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.email

import cats.effect.IO
import gospeak.core.services.email.EmailSrv
import gospeak.core.services.email.EmailSrv.Email
import gospeak.libs.scala.domain.Done

// useful for dev
class ConsoleEmailSrv extends EmailSrv {
  override def send(email: Email): IO[Done] = IO {
    println(
      s"""EmailSrv.send(
         |  from: ${email.from.format},
         |  to: ${email.to.map(_.format).mkString(", ")},
         |  subject: ${email.subject},
         |  content:
         |${email.content.value}
         |)""".stripMargin)
    Done
  }
} 
Example 48
Source File: CloudinarySrvImpl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.cloudinary

import cats.effect.IO
import gospeak.core.domain.{ExternalEvent, Group, Partner, User}
import gospeak.core.services.cloudinary.CloudinarySrv
import gospeak.core.services.upload.UploadConf
import gospeak.libs.cloudinary.CloudinaryClient
import gospeak.libs.cloudinary.domain.CloudinaryUploadRequest
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.{Avatar, Banner, Logo}

class CloudinarySrvImpl(client: CloudinaryClient) extends CloudinarySrv {
  override def signRequest(params: Map[String, String]): Either[String, String] = client.sign(params)

  override def uploadAvatar(user: User): IO[Avatar] = {
    client.upload(CloudinaryUploadRequest(
      file = user.avatar.value,
      folder = CloudinarySrvImpl.userFolder(user),
      publicId = CloudinarySrvImpl.userAvatarFile
    )).flatMap(_.toIO(new IllegalStateException(_)))
      .flatMap(_.toUrl.toIO)
      .map(_.transform(Seq("ar_1", "c_crop")).toUrl).map(Avatar)
  }

  override def uploadGroupLogo(group: Group, logo: Logo): IO[Logo] = {
    client.upload(CloudinaryUploadRequest(
      file = logo.value,
      folder = CloudinarySrvImpl.groupFolder(group),
      publicId = CloudinarySrvImpl.groupLogoFile
    )).flatMap(_.toIO(new IllegalStateException(_)))
      .flatMap(_.toUrl.toIO)
      .map(_.transform(Seq("ar_1", "c_crop")).toUrl).map(Logo)
  }

  override def uploadGroupBanner(group: Group, banner: Banner): IO[Banner] = {
    client.upload(CloudinaryUploadRequest(
      file = banner.value,
      folder = CloudinarySrvImpl.groupFolder(group),
      publicId = CloudinarySrvImpl.groupBannerFile
    )).flatMap(_.toIO(new IllegalStateException(_)))
      .flatMap(_.toUrl.toIO)
      .map(_.transform(Seq("ar_3", "c_crop")).toUrl).map(Banner)
  }

  override def uploadPartnerLogo(group: Group, partner: Partner): IO[Logo] = {
    client.upload(CloudinaryUploadRequest(
      file = partner.logo.value,
      folder = CloudinarySrvImpl.groupPartnerFolder(group),
      publicId = CloudinarySrvImpl.groupPartnerFile(Some(partner.slug.value))
    )).flatMap(_.toIO(new IllegalStateException(_)))
      .flatMap(_.toUrl.toIO)
      .map(_.transform(Seq("ar_1", "c_crop")).toUrl).map(Logo)
  }

  override def uploadExternalEventLogo(event: ExternalEvent, logo: Logo): IO[Logo] = {
    client.upload(CloudinaryUploadRequest(
      file = logo.value,
      folder = CloudinarySrvImpl.extEventFolder(),
      publicId = CloudinarySrvImpl.extEventLogoFile(Some(event.name.value))
    )).flatMap(_.toIO(new IllegalStateException(_)))
      .flatMap(_.toUrl.toIO)
      .map(_.transform(Seq("ar_1", "c_crop")).toUrl).map(Logo)
  }
}

object CloudinarySrvImpl {
  def from(conf: UploadConf.Cloudinary): CloudinarySrvImpl =
    new CloudinarySrvImpl(new CloudinaryClient(CloudinaryClient.Conf(
      cloudName = conf.cloudName,
      uploadPreset = conf.uploadPreset,
      creds = conf.creds)))

  def userAvatarFile: Option[String] = Some("avatar")

  def groupLogoFile: Option[String] = Some("logo")

  def groupBannerFile: Option[String] = Some("banner")

  def groupPartnerFile(partnerSlug: Option[String]): Option[String] = partnerSlug.filter(_.nonEmpty)

  def groupSlackBotFile: Option[String] = Some("slack-bot-avatar")

  def extEventLogoFile(eventName: Option[String]): Option[String] = eventName.filter(_.nonEmpty)


  def userFolder(user: User): Option[String] = Some(s"users/${user.slug.value}_${user.id.value}")

  def groupFolder(group: Group): Option[String] = Some(s"groups/${group.slug.value}_${group.id.value}")

  def groupPartnerFolder(group: Group): Option[String] = groupFolder(group).map(_ + "/partners")

  def extEventFolder(): Option[String] = Some(s"ext-events")
} 
Example 49
Source File: SlackSrvImpl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.slack

import cats.data.EitherT
import cats.effect.IO
import gospeak.core.services.slack.SlackSrv
import gospeak.core.services.slack.domain.SlackAction.PostMessage
import gospeak.core.services.slack.domain._
import gospeak.infra.services.slack.SlackSrvImpl._
import gospeak.libs.scala.Crypto.AesSecretKey
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.{CustomException, Markdown}
import gospeak.libs.slack.{SlackClient, domain => api}
import io.circe.Json

import scala.util.Try

// SlackSrv should not use Slack classes in the API, it's independent and the implementation should do the needed conversion
class SlackSrvImpl(client: SlackClient) extends SlackSrv {
  override def getInfos(token: SlackToken, key: AesSecretKey): IO[SlackTokenInfo] =
    toSlack(token, key).toIO.flatMap(client.info).map(_.map(toGospeak)).flatMap(toIO)

  override def exec(action: SlackAction, data: Json, creds: SlackCredentials, key: AesSecretKey): IO[Unit] = action match {
    case a: PostMessage => postMessage(a, data, creds, key).map(_ => ())
  }

  private def postMessage(action: PostMessage, data: Json, creds: SlackCredentials, key: AesSecretKey): IO[Either[api.SlackError, api.SlackMessage]] = {
    val sender = api.SlackSender.Bot(creds.name, creds.avatar.map(_.value))
    for {
      token <- toSlack(creds.token, key).toIO
      channel <- action.channel.render(data).map(toSlackName).toIO(e => CustomException(e.message))
      message <- action.message.render(data).map(toSlack).toIO(e => CustomException(e.message))
      attempt1 <- client.postMessage(token, sender, channel, message)
      attempt2 <- attempt1 match {
        case Left(api.SlackError(false, "channel_not_found", _, _)) if action.createdChannelIfNotExist =>
          (for {
            createdChannel <- EitherT(client.createChannel(token, channel))
            invitedUsers <- EitherT.liftF(if (action.inviteEverybody) {
              client.listUsers(token).flatMap(_.getOrElse(Seq()).map(_.id).map(client.inviteToChannel(token, createdChannel.id, _)).sequence)
            } else {
              IO.pure(Seq())
            })
            attempt2 <- EitherT(client.postMessage(token, sender, createdChannel.id, message))
          } yield attempt2).value
        case _ => IO.pure(attempt1)
      }
    } yield attempt2
  }

  private def toSlack(token: SlackToken, key: AesSecretKey): Try[api.SlackToken] =
    token.decode(key).map(api.SlackToken)

  private def toSlack(md: Markdown): api.SlackContent.Markdown =
    api.SlackContent.Markdown(md.value)

  private def toSlackName(name: String): api.SlackChannel.Name =
    api.SlackChannel.Name(name)

  private def toGospeak(id: api.SlackUser.Id): SlackUser.Id =
    SlackUser.Id(id.value)

  private def toGospeak(info: api.SlackTokenInfo): SlackTokenInfo =
    SlackTokenInfo(SlackTeam.Id(info.team_id.value), info.team, info.url, toGospeak(info.user_id), info.user)

  private def toIO[A](e: Either[api.SlackError, A]): IO[A] =
    e.toIO(e => CustomException(format(e)))
}

object SlackSrvImpl {
  private[slack] def format(err: api.SlackError): String =
    err.error +
      err.needed.map(" " + _).getOrElse("") +
      err.provided.map(" (provided: " + _ + ")").getOrElse("")
} 
Example 50
Source File: TwitterSrvImpl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.twitter

import cats.effect.{ContextShift, IO}
import com.danielasfregola.twitter4s.TwitterRestClient
import com.danielasfregola.twitter4s.entities.{AccessToken, ConsumerToken, Tweet}
import gospeak.core.services.twitter.{TwitterConf, TwitterSrv, domain => gs}

import scala.concurrent.ExecutionContext

class TwitterSrvImpl(conf: TwitterConf) extends TwitterSrv {
  implicit private val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  private val consumerToken = ConsumerToken(key = conf.consumerKey, secret = conf.consumerSecret.decode)
  private val accessToken = AccessToken(key = conf.accessKey, secret = conf.accessSecret.decode)
  private val restClient = TwitterRestClient(consumerToken, accessToken)

  def tweet(msg: String): IO[gs.Tweet] =
    IO.fromFuture(IO(restClient.createTweet(trim(msg)))).map(fromLib)

  private def fromLib(t: Tweet): gs.Tweet =
    gs.Tweet(
      id = t.id,
      text = t.text)
} 
Example 51
Source File: SponsorRepoSql.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.storage.sql

import java.time.Instant

import cats.effect.IO
import doobie.implicits._
import doobie.util.fragment.Fragment
import gospeak.core.domain._
import gospeak.core.domain.utils.OrgaCtx
import gospeak.core.services.storage.SponsorRepo
import gospeak.infra.services.storage.sql.SponsorRepoSql._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.Mappings._
import gospeak.infra.services.storage.sql.utils.DoobieUtils._
import gospeak.infra.services.storage.sql.utils.GenericRepo
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.{Done, Page}

class SponsorRepoSql(protected[sql] val xa: doobie.Transactor[IO]) extends GenericRepo with SponsorRepo {
  override def create(data: Sponsor.Data)(implicit ctx: OrgaCtx): IO[Sponsor] =
    insert(Sponsor(ctx.group.id, data, ctx.info)).run(xa)

  override def edit(sponsor: Sponsor.Id, data: Sponsor.Data)(implicit ctx: OrgaCtx): IO[Done] =
    update(ctx.group.id, sponsor)(data, ctx.user.id, ctx.now).run(xa)

  override def remove(sponsor: Sponsor.Id)(implicit ctx: OrgaCtx): IO[Done] = delete(ctx.group.id, sponsor).run(xa)

  override def find(sponsor: Sponsor.Id)(implicit ctx: OrgaCtx): IO[Option[Sponsor]] = selectOne(ctx.group.id, sponsor).runOption(xa)

  override def listFull(params: Page.Params)(implicit ctx: OrgaCtx): IO[Page[Sponsor.Full]] = selectPage(params).run(xa)

  override def listCurrentFull(group: Group.Id, now: Instant): IO[Seq[Sponsor.Full]] = selectCurrent(group, now).runList(xa)

  override def listAll(implicit ctx: OrgaCtx): IO[Seq[Sponsor]] = selectAll(ctx.group.id).runList(xa)

  override def listAll(contact: Contact.Id)(implicit ctx: OrgaCtx): IO[Seq[Sponsor]] = selectAll(ctx.group.id, contact).runList(xa)

  override def listAllFull(partner: Partner.Id)(implicit ctx: OrgaCtx): IO[Seq[Sponsor.Full]] = selectAllFull(ctx.group.id, partner).runList(xa)
}

object SponsorRepoSql {
  private val _ = sponsorIdMeta // for intellij not remove DoobieUtils.Mappings import
  private val table = Tables.sponsors
  val tableFull: Table = table
    .join(Tables.sponsorPacks, _.sponsor_pack_id -> _.id).get
    .join(Tables.partners, _.partner_id -> _.id).get
    .joinOpt(Tables.contacts, _.contact_id -> _.id).get
    .copy(filters = Seq(
      Filter.Bool.fromNow("active", "Is active", "s.start", "s.finish")))

  private[sql] def insert(e: Sponsor): Insert[Sponsor] = {
    val values = fr0"${e.id}, ${e.group}, ${e.partner}, ${e.pack}, ${e.contact}, ${e.start}, ${e.finish}, ${e.paid}, ${e.price.amount}, ${e.price.currency}, ${e.info.createdAt}, ${e.info.createdBy}, ${e.info.updatedAt}, ${e.info.updatedBy}"
    table.insert(e, _ => values)
  }

  private[sql] def update(group: Group.Id, sponsor: Sponsor.Id)(data: Sponsor.Data, by: User.Id, now: Instant): Update = {
    val fields = fr0"partner_id=${data.partner}, sponsor_pack_id=${data.pack}, contact_id=${data.contact}, start=${data.start}, finish=${data.finish}, paid=${data.paid}, price=${data.price.amount}, currency=${data.price.currency}, updated_at=$now, updated_by=$by"
    table.update(fields, where(group, sponsor))
  }

  private[sql] def delete(group: Group.Id, sponsor: Sponsor.Id): Delete =
    table.delete(where(group, sponsor))

  private[sql] def selectOne(group: Group.Id, pack: Sponsor.Id): Select[Sponsor] =
    table.select[Sponsor](where(group, pack))

  private[sql] def selectPage(params: Page.Params)(implicit ctx: OrgaCtx): SelectPage[Sponsor.Full, OrgaCtx] =
    tableFull.selectPage[Sponsor.Full, OrgaCtx](params, fr0"WHERE s.group_id=${ctx.group.id}")

  private[sql] def selectCurrent(group: Group.Id, now: Instant): Select[Sponsor.Full] =
    tableFull.select[Sponsor.Full](fr0"WHERE s.group_id=$group AND s.start < $now AND s.finish > $now")

  private[sql] def selectAll(group: Group.Id): Select[Sponsor] =
    table.select[Sponsor](fr0"WHERE s.group_id=$group")

  private[sql] def selectAll(group: Group.Id, contact: Contact.Id): Select[Sponsor] =
    table.select[Sponsor](fr0"WHERE s.group_id=$group AND s.contact_id=$contact")

  private[sql] def selectAllFull(group: Group.Id, partner: Partner.Id): Select[Sponsor.Full] =
    tableFull.select[Sponsor.Full](fr0"WHERE s.group_id=$group AND s.partner_id=$partner")

  private def where(group: Group.Id, sponsor: Sponsor.Id): Fragment =
    fr0"WHERE s.group_id=$group AND s.id=$sponsor"
} 
Example 52
Source File: ContactRepoSql.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.storage.sql

import java.time.Instant

import cats.effect.IO
import doobie.implicits._
import doobie.util.fragment.Fragment
import gospeak.core.domain._
import gospeak.core.domain.utils.OrgaCtx
import gospeak.core.services.storage.ContactRepo
import gospeak.infra.services.storage.sql.ContactRepoSql._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.Mappings._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.{Delete, Insert, Select, Update}
import gospeak.infra.services.storage.sql.utils.GenericRepo
import gospeak.libs.scala.domain.{Done, EmailAddress}

class ContactRepoSql(protected[sql] val xa: doobie.Transactor[IO]) extends GenericRepo with ContactRepo {
  override def create(data: Contact.Data)(implicit ctx: OrgaCtx): IO[Contact] = insert(Contact(data, ctx.info)).run(xa)

  override def edit(contact: Contact.Id, data: Contact.Data)(implicit ctx: OrgaCtx): IO[Done] = update(contact, data)(ctx.user.id, ctx.now).run(xa)

  override def remove(partner: Partner.Id, contact: Contact.Id)(implicit ctx: OrgaCtx): IO[Done] = delete(ctx.group.id, partner, contact)(ctx.user.id, ctx.now).run(xa)

  override def find(id: Contact.Id): IO[Option[Contact]] = selectOne(id).runOption(xa)

  override def list(partner: Partner.Id): IO[Seq[Contact]] = selectAll(partner).runList(xa)

  override def exists(partner: Partner.Id, email: EmailAddress): IO[Boolean] = selectOne(partner, email).runExists(xa)
}

object ContactRepoSql {
  private val _ = contactIdMeta // for intellij not remove DoobieUtils.Mappings import
  private val table = Tables.contacts

  private[sql] def insert(e: Contact): Insert[Contact] = {
    val values = fr0"${e.id}, ${e.partner}, ${e.firstName}, ${e.lastName}, ${e.email}, ${e.notes}, ${e.info.createdAt}, ${e.info.createdBy}, ${e.info.updatedAt}, ${e.info.updatedBy}"
    table.insert[Contact](e, _ => values)
  }

  private[sql] def update(contact: Contact.Id, data: Contact.Data)(by: User.Id, now: Instant): Update = {
    val fields = fr0"first_name=${data.firstName}, last_name=${data.lastName}, email=${data.email}, notes=${data.notes}, updated_at=$now, updated_by=$by"
    table.update(fields, where(contact))
  }

  private[sql] def delete(group: Group.Id, partner: Partner.Id, contact: Contact.Id)(by: User.Id, now: Instant): Delete =
    table.delete(where(contact))

  private[sql] def selectAll(partner: Partner.Id): Select[Contact] =
    table.select[Contact](where(partner))

  private[sql] def selectOne(id: Contact.Id): Select[Contact] =
    table.select[Contact](where(id))

  private[sql] def selectOne(partner: Partner.Id, email: EmailAddress): Select[Contact] =
    table.select[Contact](where(partner, email))

  private def where(partner: Partner.Id): Fragment = fr0"WHERE ct.partner_id=$partner"

  private def where(partner: Partner.Id, email: EmailAddress): Fragment = fr0"WHERE ct.partner_id=$partner AND ct.email=$email"

  private def where(id: Contact.Id): Fragment = fr0"WHERE ct.id=$id"
} 
Example 53
Source File: CommentRepoSql.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.storage.sql

import cats.effect.IO
import doobie.implicits._
import gospeak.core.domain.utils.{OrgaCtx, UserCtx}
import gospeak.core.domain.{Comment, Event, Proposal}
import gospeak.core.services.storage.CommentRepo
import gospeak.infra.services.storage.sql.CommentRepoSql._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.Mappings._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.{Insert, Select}
import gospeak.infra.services.storage.sql.utils.GenericRepo
import gospeak.libs.scala.Extensions._

class CommentRepoSql(protected[sql] val xa: doobie.Transactor[IO]) extends GenericRepo with CommentRepo {
  override def addComment(event: Event.Id, data: Comment.Data)(implicit ctx: UserCtx): IO[Comment] =
    insert(event, Comment.create(data, Comment.Kind.Event, ctx.user.id, ctx.now)).run(xa)

  override def addComment(proposal: Proposal.Id, data: Comment.Data)(implicit ctx: UserCtx): IO[Comment] =
    insert(proposal, Comment.create(data, Comment.Kind.Proposal, ctx.user.id, ctx.now)).run(xa)

  override def addOrgaComment(proposal: Proposal.Id, data: Comment.Data)(implicit ctx: OrgaCtx): IO[Comment] =
    insert(proposal, Comment.create(data, Comment.Kind.ProposalOrga, ctx.user.id, ctx.now)).run(xa)

  override def getComments(event: Event.Id): IO[Seq[Comment.Full]] = selectAll(event, Comment.Kind.Event).runList(xa)

  override def getComments(proposal: Proposal.Id)(implicit ctx: UserCtx): IO[Seq[Comment.Full]] = selectAll(proposal, Comment.Kind.Proposal).runList(xa)

  override def getOrgaComments(proposal: Proposal.Id)(implicit ctx: OrgaCtx): IO[Seq[Comment.Full]] = selectAll(proposal, Comment.Kind.ProposalOrga).runList(xa)
}

object CommentRepoSql {
  private val _ = commentIdMeta // for intellij not remove DoobieUtils.Mappings import
  private val table = Tables.comments
  private val tableFull = table
    .join(Tables.users, _.created_by -> _.id)
    .flatMap(_.dropField(_.event_id))
    .flatMap(_.dropField(_.proposal_id)).get

  private[sql] def insert(e: Event.Id, c: Comment): Insert[Comment] = {
    val values = fr0"$e, ${Option.empty[Proposal.Id]}, ${c.id}, ${c.kind}, ${c.answers}, ${c.text}, ${c.createdAt}, ${c.createdBy}"
    table.insert(c, _ => values)
  }

  private[sql] def insert(e: Proposal.Id, c: Comment): Insert[Comment] = {
    val values = fr0"${Option.empty[Event.Id]}, $e, ${c.id}, ${c.kind}, ${c.answers}, ${c.text}, ${c.createdAt}, ${c.createdBy}"
    table.insert(c, _ => values)
  }

  private[sql] def selectAll(event: Event.Id, kind: Comment.Kind): Select[Comment.Full] =
    tableFull.select[Comment.Full](fr0"WHERE co.event_id=$event AND co.kind=$kind")

  private[sql] def selectAll(proposal: Proposal.Id, kind: Comment.Kind): Select[Comment.Full] =
    tableFull.select[Comment.Full](fr0"WHERE co.proposal_id=$proposal AND co.kind=$kind")
} 
Example 54
Source File: ExternalCfpRepoSql.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.storage.sql

import java.time.Instant

import cats.data.NonEmptyList
import cats.effect.IO
import doobie.implicits._
import gospeak.core.domain._
import gospeak.core.domain.utils.{Info, UserAwareCtx, UserCtx}
import gospeak.core.services.storage.ExternalCfpRepo
import gospeak.infra.services.storage.sql.ExternalCfpRepoSql._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.Mappings._
import gospeak.infra.services.storage.sql.utils.DoobieUtils._
import gospeak.infra.services.storage.sql.utils.GenericRepo
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.{Done, Page}

class ExternalCfpRepoSql(protected[sql] val xa: doobie.Transactor[IO]) extends GenericRepo with ExternalCfpRepo {
  override def create(event: ExternalEvent.Id, data: ExternalCfp.Data)(implicit ctx: UserCtx): IO[ExternalCfp] =
    insert(ExternalCfp(data, event, Info(ctx.user.id, ctx.now))).run(xa)

  override def edit(cfp: ExternalCfp.Id)(data: ExternalCfp.Data)(implicit ctx: UserCtx): IO[Done] =
    update(cfp)(data, ctx.user.id, ctx.now).run(xa)

  override def listAllIds(): IO[Seq[ExternalCfp.Id]] = selectAllIds().runList(xa)

  override def listAll(event: ExternalEvent.Id): IO[Seq[ExternalCfp]] = selectAll(event).runList(xa)

  override def listIncoming(params: Page.Params)(implicit ctx: UserAwareCtx): IO[Page[CommonCfp]] = selectCommonPageIncoming(params).run(xa)

  override def listDuplicatesFull(p: ExternalCfp.DuplicateParams): IO[Seq[ExternalCfp.Full]] = selectDuplicatesFull(p).runList(xa)

  override def findFull(cfp: ExternalCfp.Id): IO[Option[ExternalCfp.Full]] = selectOneFull(cfp).runOption(xa)

  override def findCommon(cfp: Cfp.Slug): IO[Option[CommonCfp]] = selectOneCommon(cfp).runOption(xa)

  override def findCommon(cfp: ExternalCfp.Id): IO[Option[CommonCfp]] = selectOneCommon(cfp).runOption(xa)
}

object ExternalCfpRepoSql {
  private val _ = externalCfpIdMeta // for intellij not remove DoobieUtils.Mappings import
  private val table = Tables.externalCfps
  private val tableFull = table
    .join(Tables.externalEvents.dropFields(_.name.startsWith("location_")), _.event_id -> _.id).get
  private val commonTable = Table(
    name = "((SELECT c.name, g.logo, c.begin, c.close, g.location, c.description, c.tags, null as ext_id, null  as ext_url, null    as ext_event_start, null     as ext_event_finish, null  as ext_event_url, null          as ext_tickets_url, null         as ext_videos_url, null as twitter_account, null as twitter_hashtag, c.slug as int_slug, g.id as group_id, g.slug as group_slug FROM cfps c INNER JOIN groups g ON c.group_id=g.id) " +
      "UNION (SELECT e.name, e.logo, c.begin, c.close, e.location, c.description, e.tags, c.id as ext_id, c.url as ext_url, e.start as ext_event_start, e.finish as ext_event_finish, e.url as ext_event_url, e.tickets_url as ext_tickets_url, e.videos_url as ext_videos_url,       e.twitter_account,       e.twitter_hashtag, null   as int_slug, null as group_id,   null as group_slug FROM external_cfps c INNER JOIN external_events e ON c.event_id=e.id))",
    prefix = "c",
    joins = Seq(),
    fields = Seq(
      "name", "logo", "begin", "close", "location", "description", "tags",
      "ext_id", "ext_url", "ext_event_start", "ext_event_finish", "ext_event_url", "ext_tickets_url", "ext_videos_url", "twitter_account", "twitter_hashtag",
      "int_slug", "group_id", "group_slug").map(Field(_, "c")),
    aggFields = Seq(),
    customFields = Seq(),
    sorts = Sorts("close", "close date", Field("close", "c"), Field("name", "c")),
    search = Seq("name", "description", "tags").map(Field(_, "c")),
    filters = Seq())

  private[sql] def insert(e: ExternalCfp): Insert[ExternalCfp] = {
    val values = fr0"${e.id}, ${e.event}, ${e.description}, ${e.begin}, ${e.close}, ${e.url}, ${e.info.createdAt}, ${e.info.createdBy}, ${e.info.updatedAt}, ${e.info.updatedBy}"
    table.insert[ExternalCfp](e, _ => values)
  }

  private[sql] def update(id: ExternalCfp.Id)(e: ExternalCfp.Data, by: User.Id, now: Instant): Update = {
    val fields = fr0"description=${e.description}, begin=${e.begin}, close=${e.close}, url=${e.url}, updated_at=$now, updated_by=$by"
    table.update(fields, fr0"WHERE id=$id")
  }

  private[sql] def selectAllIds(): Select[ExternalCfp.Id] =
    table.select[ExternalCfp.Id](Seq(Field("id", "ec")))

  private[sql] def selectAll(id: ExternalEvent.Id): Select[ExternalCfp] =
    table.select[ExternalCfp](fr0"WHERE ec.event_id=$id")

  private[sql] def selectOneFull(id: ExternalCfp.Id): Select[ExternalCfp.Full] =
    tableFull.selectOne[ExternalCfp.Full](fr0"WHERE ec.id=$id")

  private[sql] def selectOneCommon(slug: Cfp.Slug): Select[CommonCfp] =
    commonTable.selectOne[CommonCfp](fr0"WHERE c.slug=$slug")

  private[sql] def selectOneCommon(id: ExternalCfp.Id): Select[CommonCfp] =
    commonTable.selectOne[CommonCfp](fr0"WHERE c.id=$id")

  private[sql] def selectCommonPageIncoming(params: Page.Params)(implicit ctx: UserAwareCtx): SelectPage[CommonCfp, UserAwareCtx] =
    commonTable.selectPage[CommonCfp, UserAwareCtx](params, fr0"WHERE (c.close IS NULL OR c.close >= ${ctx.now})")

  private[sql] def selectDuplicatesFull(p: ExternalCfp.DuplicateParams): Select[ExternalCfp.Full] = {
    val filters = Seq(
      p.cfpUrl.map(v => fr0"ec.url LIKE ${"%" + v + "%"}"),
      p.cfpEndDate.map(v => fr0"ec.close=$v")
    ).flatten
    if (filters.isEmpty) {
      tableFull.select[ExternalCfp.Full](fr0"WHERE ec.id='no-match'")
    } else {
      tableFull.select[ExternalCfp.Full](fr0"WHERE " ++ filters.reduce(_ ++ fr0" OR " ++ _))
    }
  }
} 
Example 55
Source File: ExternalEventRepoSql.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.storage.sql

import java.time.Instant

import cats.effect.IO
import doobie.implicits._
import gospeak.core.domain.utils.{Info, UserAwareCtx, UserCtx}
import gospeak.core.domain.{CommonEvent, Event, ExternalEvent, User}
import gospeak.core.services.storage.ExternalEventRepo
import gospeak.infra.services.storage.sql.ExternalEventRepoSql._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.Mappings._
import gospeak.infra.services.storage.sql.utils.DoobieUtils._
import gospeak.infra.services.storage.sql.utils.{GenericQuery, GenericRepo}
import gospeak.libs.scala.domain.{Done, Logo, Page, Tag}

class ExternalEventRepoSql(protected[sql] val xa: doobie.Transactor[IO]) extends GenericRepo with ExternalEventRepo {
  override def create(data: ExternalEvent.Data)(implicit ctx: UserCtx): IO[ExternalEvent] =
    insert(ExternalEvent(data, Info(ctx.user.id, ctx.now))).run(xa)

  override def edit(id: ExternalEvent.Id)(data: ExternalEvent.Data)(implicit ctx: UserCtx): IO[Done] =
    update(id)(data, ctx.user.id, ctx.now).run(xa)

  override def listAllIds()(implicit ctx: UserAwareCtx): IO[Seq[ExternalEvent.Id]] = selectAllIds().runList(xa)

  override def list(params: Page.Params)(implicit ctx: UserCtx): IO[Page[ExternalEvent]] = selectPage(params).run(xa)

  override def listCommon(params: Page.Params)(implicit ctx: UserAwareCtx): IO[Page[CommonEvent]] = selectPageCommon(params).run(xa)

  override def find(id: ExternalEvent.Id): IO[Option[ExternalEvent]] = selectOne(id).runOption(xa)

  override def listTags(): IO[Seq[Tag]] = selectTags().runList(xa).map(_.flatten.distinct)

  override def listLogos(): IO[Seq[Logo]] = selectLogos().runList(xa).map(_.flatten.distinct)
}

object ExternalEventRepoSql {

  import GenericQuery._

  private val _ = externalEventIdMeta // for intellij not remove DoobieUtils.Mappings import
  val table: Table = Tables.externalEvents.copy(filters = Seq(
    Filter.Enum.fromEnum("type", "Type", "ee.kind", Seq(
      "conference" -> Event.Kind.Conference.value,
      "meetup" -> Event.Kind.Meetup.value,
      "training" -> Event.Kind.Training.value,
      "private event" -> Event.Kind.PrivateEvent.value)),
    Filter.Bool.fromNullable("video", "With video", "ee.videos_url")))
  private val tableSelect = table.dropFields(_.name.startsWith("location_"))
  val commonTable: Table = Table(
    name = "((SELECT e.name, e.kind, e.start, v.address as location, g.social_twitter as twitter_account, null as twitter_hashtag, e.tags, null as ext_id, null   as ext_logo, null          as ext_description, null  as ext_url, null          as ext_tickets, null         as ext_videos, e.id as int_id, e.slug as int_slug, e.description as int_description, g.id as int_group_id, g.slug as int_group_slug, g.name as int_group_name, g.logo as int_group_logo, c.id as int_cfp_id, c.slug as int_cfp_slug, c.name as int_cfp_name, v.id as int_venue_id, p.name as int_venue_name, p.logo as int_venue_logo, e.created_at, e.created_by, e.updated_at, e.updated_by FROM events e INNER JOIN groups g ON e.group_id=g.id LEFT OUTER JOIN cfps c ON e.cfp_id=c.id LEFT OUTER JOIN venues v ON e.venue=v.id LEFT OUTER JOIN partners p ON v.partner_id=p.id WHERE e.published IS NOT NULL) " +
      "UNION (SELECT e.name, e.kind, e.start,            e.location,                   e.twitter_account,       e.twitter_hashtag, e.tags, e.id as ext_id, e.logo as ext_logo, e.description as ext_description, e.url as ext_url, e.tickets_url as ext_tickets, e.videos_url as ext_videos, null as int_id, null   as int_slug, null          as int_description, null as int_group_id, null   as int_group_slug, null   as int_group_name, null   as int_group_logo, null as int_cfp_id, null   as int_cfp_slug, null   as int_cfp_name, null as int_venue_id, null   as int_venue_name, null   as int_venue_logo, e.created_at, e.created_by, e.updated_at, e.updated_by FROM external_events e))",
    prefix = "e",
    joins = Seq(),
    fields = Seq(
      "name", "kind", "start", "location", "twitter_account", "twitter_hashtag", "tags",
      "ext_id", "ext_logo", "ext_description", "ext_url", "ext_tickets", "ext_videos",
      "int_id", "int_slug", "int_description", "int_group_id", "int_group_slug", "int_group_name", "int_group_logo", "int_cfp_id", "int_cfp_slug", "int_cfp_name", "int_venue_id", "int_venue_name", "int_venue_logo",
      "created_at", "created_by", "updated_at", "updated_by").map(Field(_, "e")),
    aggFields = Seq(),
    customFields = Seq(),
    sorts = Sorts("start", Field("-start", "e"), Field("-created_at", "e")),
    search = Seq("name", "kind", "location", "twitter_account", "tags", "int_group_name", "int_cfp_name", "int_description", "ext_description").map(Field(_, "e")),
    filters = Seq(
      Filter.Enum.fromEnum("type", "Type", "e.kind", Seq(
        "conference" -> Event.Kind.Conference.value,
        "meetup" -> Event.Kind.Meetup.value,
        "training" -> Event.Kind.Training.value,
        "private event" -> Event.Kind.PrivateEvent.value)),
      Filter.Bool.fromNullable("video", "With video", "e.ext_videos"),
      Filter.Bool("past", "Is past", aggregation = false, ctx => fr0"e.start < ${ctx.now}", ctx => fr0"e.start > ${ctx.now}")))

  private[sql] def insert(e: ExternalEvent): Insert[ExternalEvent] = {
    val values = fr0"${e.id}, ${e.name}, ${e.kind}, ${e.logo}, ${e.description}, ${e.start}, ${e.finish}, " ++ insertLocation(e.location) ++ fr0", ${e.url}, ${e.tickets}, ${e.videos}, ${e.twitterAccount}, ${e.twitterHashtag}, ${e.tags}, " ++ insertInfo(e.info)
    table.insert[ExternalEvent](e, _ => values)
  }

  private[sql] def update(id: ExternalEvent.Id)(e: ExternalEvent.Data, by: User.Id, now: Instant): Update = {
    val fields = fr0"name=${e.name}, kind=${e.kind}, logo=${e.logo}, description=${e.description}, start=${e.start}, finish=${e.finish}, " ++ updateLocation(e.location) ++ fr0", url=${e.url}, tickets_url=${e.tickets}, videos_url=${e.videos}, twitter_account=${e.twitterAccount}, twitter_hashtag=${e.twitterHashtag}, tags=${e.tags}, updated_at=$now, updated_by=$by"
    table.update(fields, fr0"WHERE id=$id")
  }

  private[sql] def selectOne(id: ExternalEvent.Id): Select[ExternalEvent] =
    tableSelect.selectOne[ExternalEvent](fr0"WHERE ee.id=$id")

  private[sql] def selectAllIds()(implicit ctx: UserAwareCtx): Select[ExternalEvent.Id] =
    table.select[ExternalEvent.Id](Seq(Field("id", "ee")))

  private[sql] def selectPage(params: Page.Params)(implicit ctx: UserCtx): SelectPage[ExternalEvent, UserCtx] =
    tableSelect.selectPage[ExternalEvent, UserCtx](params)

  private[sql] def selectPageCommon(params: Page.Params)(implicit ctx: UserAwareCtx): SelectPage[CommonEvent, UserAwareCtx] =
    commonTable.selectPage[CommonEvent, UserAwareCtx](params)

  private[sql] def selectTags(): Select[Seq[Tag]] =
    table.select[Seq[Tag]](Seq(Field("tags", "ee")))

  private[sql] def selectLogos(): Select[Option[Logo]] =
    table.select[Option[Logo]](Seq(Field("logo", "ee")), fr0"WHERE ee.logo IS NOT NULL")
} 
Example 56
Source File: SponsorPackRepoSql.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.storage.sql

import java.time.Instant

import cats.data.NonEmptyList
import cats.effect.IO
import doobie.Fragments
import doobie.implicits._
import doobie.util.fragment.Fragment
import gospeak.core.domain.utils.OrgaCtx
import gospeak.core.domain.{Group, SponsorPack, User}
import gospeak.core.services.storage.SponsorPackRepo
import gospeak.infra.services.storage.sql.SponsorPackRepoSql._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.Mappings._
import gospeak.infra.services.storage.sql.utils.DoobieUtils.{Insert, Select, Update}
import gospeak.infra.services.storage.sql.utils.GenericRepo
import gospeak.libs.scala.domain.{CustomException, Done}

class SponsorPackRepoSql(protected[sql] val xa: doobie.Transactor[IO]) extends GenericRepo with SponsorPackRepo {
  override def create(data: SponsorPack.Data)(implicit ctx: OrgaCtx): IO[SponsorPack] =
    insert(SponsorPack(ctx.group.id, data, ctx.info)).run(xa)

  override def edit(pack: SponsorPack.Slug, data: SponsorPack.Data)(implicit ctx: OrgaCtx): IO[Done] = {
    if (data.slug != pack) {
      find(data.slug).flatMap {
        case None => update(ctx.group.id, pack)(data, ctx.user.id, ctx.now).run(xa)
        case _ => IO.raiseError(CustomException(s"You already have a sponsor pack with slug ${data.slug}"))
      }
    } else {
      update(ctx.group.id, pack)(data, ctx.user.id, ctx.now).run(xa)
    }
  }

  override def disable(pack: SponsorPack.Slug)(implicit ctx: OrgaCtx): IO[Done] =
    setActive(ctx.group.id, pack)(active = false, ctx.user.id, ctx.now).run(xa)

  override def enable(pack: SponsorPack.Slug)(implicit ctx: OrgaCtx): IO[Done] =
    setActive(ctx.group.id, pack)(active = true, ctx.user.id, ctx.now).run(xa)

  override def find(pack: SponsorPack.Slug)(implicit ctx: OrgaCtx): IO[Option[SponsorPack]] = selectOne(ctx.group.id, pack).runOption(xa)

  override def listAll(group: Group.Id): IO[Seq[SponsorPack]] = selectAll(group).runList(xa)

  override def listAll(implicit ctx: OrgaCtx): IO[Seq[SponsorPack]] = selectAll(ctx.group.id).runList(xa)

  override def listActives(group: Group.Id): IO[Seq[SponsorPack]] = selectActives(group).runList(xa)

  override def listActives(implicit ctx: OrgaCtx): IO[Seq[SponsorPack]] = selectActives(ctx.group.id).runList(xa)
}

object SponsorPackRepoSql {
  private val _ = sponsorPackIdMeta // for intellij not remove DoobieUtils.Mappings import
  private val table = Tables.sponsorPacks

  private[sql] def insert(e: SponsorPack): Insert[SponsorPack] = {
    val values = fr0"${e.id}, ${e.group}, ${e.slug}, ${e.name}, ${e.description}, ${e.price.amount}, ${e.price.currency}, ${e.duration}, ${e.active}, ${e.info.createdAt}, ${e.info.createdBy}, ${e.info.updatedAt}, ${e.info.updatedBy}"
    table.insert(e, _ => values)
  }

  private[sql] def update(group: Group.Id, pack: SponsorPack.Slug)(data: SponsorPack.Data, by: User.Id, now: Instant): Update = {
    val fields = fr0"slug=${data.slug}, name=${data.name}, description=${data.description}, price=${data.price.amount}, currency=${data.price.currency}, duration=${data.duration}, updated_at=$now, updated_by=$by"
    table.update(fields, where(group, pack))
  }

  private[sql] def setActive(group: Group.Id, pack: SponsorPack.Slug)(active: Boolean, by: User.Id, now: Instant): Update =
    table.update(fr0"active=$active, updated_at=$now, updated_by=$by", where(group, pack))

  private[sql] def selectOne(group: Group.Id, pack: SponsorPack.Slug): Select[SponsorPack] =
    table.select[SponsorPack](where(group, pack))

  private[sql] def selectAll(ids: NonEmptyList[SponsorPack.Id]): Select[SponsorPack] =
    table.select[SponsorPack](fr0"WHERE " ++ Fragments.in(fr"sp.id", ids))

  private[sql] def selectAll(group: Group.Id): Select[SponsorPack] =
    table.select[SponsorPack](where(group))

  private[sql] def selectActives(group: Group.Id): Select[SponsorPack] = {
    val active = true
    table.select[SponsorPack](where(group) ++ fr0" AND sp.active=$active")
  }

  private def where(group: Group.Id, slug: SponsorPack.Slug): Fragment =
    fr0"WHERE sp.group_id=$group AND sp.slug=$slug"

  private def where(group: Group.Id): Fragment =
    fr0"WHERE sp.group_id=$group"
} 
Example 57
Source File: VideoSrvImpl.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.infra.services.video

import cats.effect.IO
import gospeak.core.domain.Video
import gospeak.core.domain.Video.PlaylistRef
import gospeak.core.services.video.{VideoSrv, YoutubeConf}
import gospeak.libs.scala.Cache
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.{CustomException, Url}
import gospeak.libs.youtube.YoutubeClient
import gospeak.libs.youtube.domain.{YoutubeErrors, YoutubePlaylist}

import scala.util.Try

class VideoSrvImpl(youtubeOpt: Option[YoutubeClient]) extends VideoSrv {
  private val getChannelIdCache: Url.Videos.Channel => IO[Url.Videos.Channel.Id] = Cache.memoizeIO {
    case u: Url.YouTube.Channel => withYoutube(_.getChannelId(u).flatMap(formatError))
    case _: Url.Vimeo.Channel => withVimeo()
  }
  override val youtube: Boolean = youtubeOpt.isDefined
  override val vimeo: Boolean = false
  override val infoq: Boolean = false

  override def getChannelId(url: Url.Videos.Channel): IO[Url.Videos.Channel.Id] = getChannelIdCache(url)

  override def listVideos(url: Url.Videos): IO[List[Video.Data]] = url match {
    case u: Url.YouTube.Channel => withYoutube(c => c.getChannelId(u).flatMap(formatError).flatMap(id => listYoutubeChannelVideos(c, id)))
    case u: Url.YouTube.Playlist => withYoutube(c => c.getPlaylist(u).flatMap(formatError).flatMap(p => listYoutubePlaylistVideos(c, p)))
    case _: Url.Vimeo.Channel => withVimeo()
    case _: Url.Vimeo.Showcase => withVimeo()
    case _: Url.Infoq.Topic => withInfoq()
  }

  private def listYoutubeChannelVideos(client: YoutubeClient, channelId: Url.Videos.Channel.Id, pageToken: String = ""): IO[List[Video.Data]] = for {
    page <- client.listChannelVideos(channelId, pageToken).flatMap(formatError)
    videos <- getYoutubeVideoDetails(page.items.map(_.id), None)(client)
    allPages <- page.nextPageToken.map(listYoutubeChannelVideos(client, channelId, _).map(nextPage => videos ++ nextPage)).getOrElse(IO.pure(videos))
  } yield allPages

  private def listYoutubePlaylistVideos(client: YoutubeClient, playlist: YoutubePlaylist, pageToken: String = ""): IO[List[Video.Data]] = for {
    videoPage <- client.listPlaylistVideos(playlist.id, pageToken).flatMap(formatError)
    videoDetails <- getYoutubeVideoDetails(videoPage.items.map(_.id), Some(playlist))(client)
    allPages <- videoPage.nextPageToken.map(listYoutubePlaylistVideos(client, playlist, _).map(nextPage => videoDetails ++ nextPage)).getOrElse(IO.pure(videoDetails))
  } yield allPages

  private def getYoutubeVideoDetails(videoIds: Seq[Url.Video.Id], playlist: Option[YoutubePlaylist])(client: YoutubeClient): IO[List[Video.Data]] =
    client.getVideoDetails(videoIds).flatMap(formatError).map(_.map(Video.Data.from(_, playlist.map(p => PlaylistRef(p.id, p.title)))).sequence).flatMap(_.toIO)

  private def formatError[A](errors: Either[YoutubeErrors, A]): IO[A] =
    errors.toIO(e => CustomException(s"YouTube error ${e.code}: ${e.message.getOrElse("")}" + e.errors.map("\n" + _).mkString))

  private def withYoutube[T](f: YoutubeClient => IO[T]): IO[T] = youtubeOpt.map(f).getOrElse(IO.raiseError(CustomException("YoutubeClient not available")))

  private def withVimeo[T](): IO[T] = IO.raiseError(CustomException("VimeoClient not available"))

  private def withInfoq[T](): IO[T] = IO.raiseError(CustomException("InfoqClient not available"))
}

object VideoSrvImpl {
  def from(appName: String, youtubeConf: YoutubeConf): Try[VideoSrvImpl] = for {
    youtubeClient <- youtubeConf.secret.map(YoutubeClient.create(_, appName)).sequence
  } yield new VideoSrvImpl(youtubeClient)
} 
Example 58
Source File: CloudinaryClient.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.libs.cloudinary

import java.time.Instant

import cats.effect.IO
import gospeak.libs.cloudinary.CloudinaryJson._
import gospeak.libs.cloudinary.domain.{CloudinaryUploadRequest, CloudinaryUploadResponse}
import gospeak.libs.http.HttpClient
import gospeak.libs.scala.Crypto
import gospeak.libs.scala.Extensions._
import gospeak.libs.scala.domain.Creds
import io.circe.parser.decode

class CloudinaryClient(conf: CloudinaryClient.Conf) {
  private val baseUrl = "https://api.cloudinary.com/v1_1"
  private val ignoreOnSign = Set("api_key", "file")

  // see https://cloudinary.com/documentation/upload_images#generating_authentication_signatures
  def sign(params: Map[String, String]): Either[String, String] =
    withCreds((_, creds) => sign(creds, params))

  // see https://cloudinary.com/documentation/upload_images#uploading_with_a_direct_call_to_the_api
  def upload(req: CloudinaryUploadRequest): IO[Either[String, CloudinaryUploadResponse]] = {
    withCreds { (cloudName, creds) =>
      val uploadUrl = s"$baseUrl/$cloudName/image/upload"
      val allParams = req.toMap ++ Map(
        "api_key" -> creds.key,
        "timestamp" -> Instant.now().getEpochSecond.toString)
      val signature = sign(creds, allParams)
      HttpClient.postForm(uploadUrl, allParams ++ Map("signature" -> signature))
        .map(r => decode[CloudinaryUploadResponse](r.body).leftMap(_.getMessage))
    }.sequence.map(_.flatMap(identity))
  }

  private def sign(creds: Creds, queryParams: Map[String, String]): String = {
    val params = queryParams
      .filterKeys(!ignoreOnSign.contains(_))
      .toList.sortBy(_._1)
      .map { case (key, value) => s"$key=$value" }.mkString("&")
    Crypto.sha1(params + creds.secret.decode)
  }

  private def withCreds[A](block: (String, Creds) => A): Either[String, A] =
    conf match {
      case CloudinaryClient.Conf(cloudName, _, Some(creds)) => Right(block(cloudName, creds))
      case _: CloudinaryClient.Conf => Left("No credentials defined for cloudinary")
    }
}

object CloudinaryClient {

  final case class Conf(cloudName: String,
                        uploadPreset: Option[String],
                        creds: Option[Creds])

} 
Example 59
Source File: SlackClient.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.libs.slack

import cats.effect.IO
import gospeak.libs.slack.SlackJson._
import gospeak.libs.slack.domain._
import gospeak.libs.http.HttpClient
import gospeak.libs.http.HttpClient.Response
import io.circe.Decoder
import io.circe.parser._

// SlackClient should not depend on core module, it's an independent lib
// scala lib: https://github.com/slack-scala-client/slack-scala-client
class SlackClient {
  private val baseUrl = "https://slack.com/api"

  // cf https://api.slack.com/methods/auth.test
  def info(token: SlackToken): IO[Either[SlackError, SlackTokenInfo]] =
    HttpClient.get(s"$baseUrl/auth.test", query = Map("token" -> token.value))
      .flatMap(parse[SlackTokenInfo])

  // cf https://api.slack.com/methods/channels.create
  def createChannel(token: SlackToken, name: SlackChannel.Name): IO[Either[SlackError, SlackChannel]] =
    HttpClient.get(s"$baseUrl/channels.create", query = Map(
      "token" -> token.value,
      "name" -> name.value
    )).flatMap(parse[SlackChannel.Single]).map(_.map(_.channel))

  // cf https://api.slack.com/methods/channels.invite
  def inviteToChannel(token: SlackToken, channel: SlackChannel.Id, user: SlackUser.Id): IO[Either[SlackError, SlackChannel]] =
    HttpClient.get(s"$baseUrl/channels.invite", query = Map(
      "token" -> token.value,
      "channel" -> channel.value,
      "user" -> user.value
    )).flatMap(parse[SlackChannel.Single]).map(_.map(_.channel))

  // cf https://api.slack.com/methods/channels.list
  def listChannels(token: SlackToken): IO[Either[SlackError, Seq[SlackChannel]]] =
    HttpClient.get(s"$baseUrl/channels.list", query = Map("token" -> token.value))
      .flatMap(parse[SlackChannel.List]).map(_.map(_.channels))

  // cf https://api.slack.com/methods/users.list
  def listUsers(token: SlackToken): IO[Either[SlackError, Seq[SlackUser]]] =
    HttpClient.get(s"$baseUrl/users.list", query = Map("token" -> token.value))
      .flatMap(parse[SlackUser.List]).map(_.map(_.members))

  // cf https://api.slack.com/methods/chat.postMessage
  def postMessage(token: SlackToken, sender: SlackSender, channel: SlackChannel.Ref, msg: SlackContent): IO[Either[SlackError, SlackMessage]] =
    HttpClient.get(s"$baseUrl/chat.postMessage", query = sender.toOpts ++ msg.toOpts ++ Map(
      "token" -> token.value,
      "channel" -> channel.value
    )).flatMap(parse[SlackMessage.Posted]).map(_.map(_.message))

  private def parse[A](res: Response)(implicit d: Decoder[A]): IO[Either[SlackError, A]] = {
    decode[A](res.body) match {
      case Right(info) => IO.pure(Right(info))
      case Left(err) => decode[SlackError](res.body) match {
        case Right(fail) => IO.pure(Left(fail))
        case Left(_) => IO.raiseError(err)
      }
    }
  }
} 
Example 60
Source File: HttpClient.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.libs.http

import java.net.URLEncoder
import java.nio.charset.StandardCharsets

import cats.effect.IO
import gospeak.libs.scala.domain.CustomException
import hammock.apache.ApacheInterpreter
import hammock.{Encoder, Entity, Hammock, HttpResponse, InterpTrans, Method, Uri}

object HttpClient {

  final case class Request(url: String,
                           query: Map[String, String] = Map(),
                           headers: Map[String, String] = Map())

  final case class Response(status: Int,
                            body: String,
                            headers: Map[String, String]) {
    def isOk: Boolean = 200 <= status && status < 400
  }

  object Response {
    def from(res: HttpResponse): Response =
      Response(
        status = res.status.code,
        headers = res.headers,
        body = res.entity match {
          case Entity.EmptyEntity => ""
          case e: Entity.StringEntity => e.body
          case e: Entity.ByteArrayEntity => new String(e.body, StandardCharsets.UTF_8)
        })
  }

  private implicit val interpreter: InterpTrans[IO] = ApacheInterpreter.instance[IO]
  private implicit val stringEncoder: Encoder[String] = (value: String) => Entity.StringEntity(value)

  def get(url: String, query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    buildUri(url, query)(request(Method.GET, _, headers))

  def postJson(url: String, body: String, query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendJson(method = Method.POST, url = url, body = body, query = query, headers = headers)

  def putJson(url: String, body: String, query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendJson(method = Method.PUT, url = url, body = body, query = query, headers = headers)

  def patchJson(url: String, body: String, query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendJson(method = Method.PATCH, url = url, body = body, query = query, headers = headers)

  def deleteJson(url: String, body: String, query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendJson(method = Method.DELETE, url = url, body = body, query = query, headers = headers)

  def postForm(url: String, body: Map[String, String], query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendForm(method = Method.POST, url = url, body = body, query = query, headers = headers)

  def putForm(url: String, body: Map[String, String], query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendForm(method = Method.PUT, url = url, body = body, query = query, headers = headers)

  def patchForm(url: String, body: Map[String, String], query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendForm(method = Method.PATCH, url = url, body = body, query = query, headers = headers)

  def deleteForm(url: String, body: Map[String, String], query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    sendForm(method = Method.DELETE, url = url, body = body, query = query, headers = headers)

  private def sendJson(method: Method, url: String, body: String, query: Map[String, String] = Map(), headers: Map[String, String] = Map()): IO[Response] =
    buildUri(url, query)(request(method, _, headers + ("Content-Type" -> "application/json"), Some(body)))

  private def sendForm(method: Method, url: String, body: Map[String, String], query: Map[String, String], headers: Map[String, String]): IO[Response] =
    buildUri(url, query)(request(method, _, headers + ("Content-Type" -> "application/x-www-form-urlencoded"), Some(buildParams(body))))

  private def buildUri(url: String, query: Map[String, String])(callback: Uri => IO[Response]): IO[Response] =
    Uri.fromString(buildUrl(url, query)).map(callback).getOrElse(IO.raiseError(CustomException(s"Invalid URI '${buildUrl(url, query)}'")))

  private def request(method: Method,
                      uri: Uri,
                      headers: Map[String, String],
                      body: Option[String] = None): IO[Response] = {
    // def requestInfo: String = s"HttpClient.$method ${uri.path}\n  headers: $headers" + body.map(b => s"\n  body: $b").getOrElse("")
    Hammock.request(method, uri, headers, body).exec[IO].map(Response.from)
    // .map { r => println(s"\n$requestInfo\n  response: ${r.status} ${r.body}"); r }
    // .recoverWith { case e => println(s"\n$requestInfo\n  response: ${e.getClass.getSimpleName}: ${e.getMessage}"); IO.raiseError(e) }
  }

  def buildUrl(url: String, query: Map[String, String]): String = {
    if (query.isEmpty) {
      url
    } else if (url.contains("?")) {
      url + "&" + buildParams(query)
    } else {
      url + "?" + buildParams(query)
    }
  }

  private def buildParams(params: Map[String, String]): String =
    params.map { case (key, value) => s"$key=${URLEncoder.encode(value, "UTF8")}" }.mkString("&")
} 
Example 61
Source File: MessageBus.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.libs.scala

import cats.effect.IO
import Extensions._

import scala.collection.mutable
import scala.reflect.ClassTag

trait MessageBus[A] {
  def subscribe[B <: A : ClassTag](f: B => IO[Unit])

  // not used
  // like subscribe but with a partial function
  // def when[B <: A : ClassTag](pf: PartialFunction[B, IO[Unit]]): Unit

  // returns the number of called subscribers
  def publish[B <: A](msg: B): IO[Int]

  // not used
  // like 'publish' with 'msg' param lazy evaluated but will call only T handlers, not 'msg' concrete type handlers
  // def publishLazy[B <: A : ClassTag](msg: => B): IO[Int]
}

class BasicMessageBus[A] extends MessageBus[A] {
  private val eventHandlers = mutable.Map[Class[_], List[_ => IO[Unit]]]()

  override def subscribe[B <: A : ClassTag](f: B => IO[Unit]): Unit = {
    val key = implicitly[ClassTag[B]].runtimeClass
    eventHandlers.put(key, eventHandlers.getOrElse(key, Nil) :+ f)
  }

  

  private def getClasses(clazz: Class[_]): List[Class[_]] = {
    val parents = Option(clazz.getSuperclass).toList ++ clazz.getInterfaces.toList
    clazz :: parents.flatMap(getClasses)
  }
} 
Example 62
Source File: MessageBusSpec.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.libs.scala

import cats.effect.IO
import gospeak.libs.scala.MessageBusSpec._
import gospeak.libs.testingutils.BaseSpec

class MessageBusSpec extends BaseSpec {
  describe("MessageBus") {
    it("should be specialized for a message type") {
      val mb = create[MyEvents]()
      mb.publish(Event1("a"))
      // mb.publish(Message("a")) // should not compile
    }
    describe("subscribe") {
      it("should register a handler based on message type") {
        val mb = create[Any]()
        var tmp = ""
        mb.subscribe[Event1](e => IO {
          tmp = e.msg
        })
        mb.publish(Event1("a")).unsafeRunSync() shouldBe 1
        tmp shouldBe "a"
        mb.publish(Msg("b")).unsafeRunSync() shouldBe 0
        tmp shouldBe "a"
        mb.publish(Event1("c")).unsafeRunSync() shouldBe 1
        tmp shouldBe "c"
      }
      it("should work with parent types") {
        val mb = create[Any]()
        var tmp = ""
        mb.subscribe[MyEvents] {
          case e: Event1 => IO {
            tmp = e.msg
          }
          case e: Event2 => IO {
            tmp = e.count.toString
          }
        }
        mb.publish(Event1("a")).unsafeRunSync() shouldBe 1
        tmp shouldBe "a"
        mb.publish(Event2(2)).unsafeRunSync() shouldBe 1
        tmp shouldBe "2"
        mb.publish(Event1("c"): MyEvents).unsafeRunSync() shouldBe 1
        tmp shouldBe "c"
      }
    }
  }
}

object MessageBusSpec {

  sealed trait MyEvents

  case class Event1(msg: String) extends MyEvents

  case class Event2(count: Int) extends MyEvents

  case class Msg(info: String)

  def create[A](): MessageBus[A] = {
    new BasicMessageBus[A]
  }
} 
Example 63
Source File: CacheSpec.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.libs.scala

import cats.effect.IO
import gospeak.libs.testingutils.BaseSpec

class CacheSpec extends BaseSpec {
  describe("Cache") {
    describe("memoize") {
      it("should compute value once") {
        var call = 0
        val mem = Cache.memoize((n: Int) => {
          call += 1
          n.toString
        })
        mem(1) shouldBe "1"
        call shouldBe 1
        mem(2) shouldBe "2"
        call shouldBe 2
        mem(1) shouldBe "1"
        call shouldBe 2
      }
    }
    describe("memoizeIO") {
      it("should compute value once") {
        var call = 0
        val mem = Cache.memoizeIO((n: Int) => {
          call += 1
          IO.pure(n.toString)
        })
        mem(1).unsafeRunSync() shouldBe "1"
        call shouldBe 1
        mem(2).unsafeRunSync() shouldBe "2"
        call shouldBe 2
        mem(1).unsafeRunSync() shouldBe "1"
        call shouldBe 2
      }
    }
  }
} 
Example 64
Source File: EmailSrv.scala    From gospeak   with Apache License 2.0 5 votes vote down vote up
package gospeak.core.services.email

import cats.effect.IO
import gospeak.core.services.email.EmailSrv.Email
import gospeak.libs.scala.domain.Done
import gospeak.libs.scala.domain.EmailAddress.Contact

trait EmailSrv {
  def send(email: Email): IO[Done]
}

object EmailSrv {

  final case class Email(from: Contact, to: Seq[Contact], subject: String, content: Content)

  sealed trait Content {
    def value: String
  }

  final case class TextContent(text: String) extends Content {
    override def value: String = text
  }

  final case class HtmlContent(html: String) extends Content {
    override def value: String = html
  }

} 
Example 65
Source File: InternalSpec.scala    From fs2-aws   with MIT License 5 votes vote down vote up
package fs2.aws.core

import cats.effect.{ ContextShift, IO }
import fs2.Stream
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.concurrent.ExecutionContext

class InternalSpec extends AnyFlatSpec with Matchers {
  implicit val ec: ExecutionContext             = ExecutionContext.global
  implicit val ioContextShift: ContextShift[IO] = IO.contextShift(ec)

  "groupBy" should "create K substreams based on K selector outputs" in {
    val k = 30
    val streams = Stream
      .emits(1 to 100000)
      .through(groupBy(i => IO(i % k)))
      .compile
      .toVector
      .unsafeRunSync

    streams.size shouldBe k
  }

  it should "split stream elements into respective substreams" in {
    val streams = Stream
      .emits(1 to 10)
      .through(groupBy(i => IO(i % 2)))
      .compile
      .toVector
      .unsafeRunSync

    streams.filter(_._1 == 0).head._2.compile.toVector.unsafeRunSync shouldBe List(2, 4, 6, 8, 10)
    streams.filter(_._1 == 1).head._2.compile.toVector.unsafeRunSync shouldBe List(1, 3, 5, 7, 9)
  }

  it should "fail on exception" in {
    val streams = Stream
      .emits(1 to 10)
      .through(groupBy(i => IO(throw new Exception())))
      .attempt
      .compile
      .toVector
      .unsafeRunSync

    streams.size        shouldBe 1
    streams.head.isLeft shouldBe true
  }
} 
Example 66
Source File: DynamoDBStreamer.scala    From fs2-aws   with MIT License 5 votes vote down vote up
import cats.effect.{ ExitCode, IO, IOApp }
import fs2.aws.dynamodb
import fs2.aws.dynamodb.parsers
import io.chrisdavenport.log4cats.SelfAwareStructuredLogger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import io.circe.Json
import io.github.howardjohn.scanamo.CirceDynamoFormat._

object DynamoDBStreamer extends IOApp {
  override def run(args: List[String]): IO[ExitCode] =
    for {
      implicit0(logger: SelfAwareStructuredLogger[IO]) <- Slf4jLogger.fromName[IO]("example")
      _ <- dynamodb
            .readFromDynamDBStream[IO](
              "dynamo_db_example",
              "arn:aws:dynamodb:us-east-1:023006903388:table/nightly-sync-events-Production/stream/2020-01-27T22:49:13.204"
            )
            .evalMap(cr => parsers.parseDynamoEvent[IO, Json](cr.record))
            .evalTap(msg => logger.info(s"received $msg"))
            .compile
            .drain
    } yield ExitCode.Success
} 
Example 67
Source File: CirisDecoderSpec.scala    From fs2-aws   with MIT License 5 votes vote down vote up
package fs2.aws.ciris;

import java.util.Date

import cats.effect.{ ContextShift, IO }
import ciris.{ ConfigException, ConfigValue }
import org.scalatest.Assertion
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import software.amazon.kinesis.common.InitialPositionInStream

import scala.concurrent.ExecutionContext.Implicits.global;

class CirisDecoderSpec extends AnyWordSpec with Matchers {
  implicit val cs: ContextShift[IO] = IO.contextShift(global)

  "InitialPositionDecoderSpec" should {

    "when decoding Either[InitialPositionInStream, Date]" can {

      // same package, so `import fs2.aws.ciris._` not necessary here
      def decode(testStr: String): Either[InitialPositionInStream, Date] =
        ConfigValue
          .default(testStr)
          .as[Either[InitialPositionInStream, Date]]
          .load[IO]
          .unsafeRunSync()

      def expectDecodeFailure(testString: String): Assertion =
        intercept[ConfigException] {
          decode(testString)
        }.getMessage should include(
          s"Unable to convert value $testString to InitialPositionInStream"
        )

      "decode supported strings as initial offsets" in {

        decode("LATEST")           should equal(Left(InitialPositionInStream.LATEST))
        decode("TRIM_HORIZON")     should equal(Left(InitialPositionInStream.TRIM_HORIZON))
        decode("TS_1592404273000") should equal(Right(new Date(1592404273000L)))

      }

      "fail to decode valid strings" in {

        expectDecodeFailure("FOOBAR")
        expectDecodeFailure("TS_FOO")
        expectDecodeFailure("TS_")
        expectDecodeFailure("_1592404273000")

      }
    }

  }

} 
Example 68
Source File: package.scala    From fs2-aws   with MIT License 5 votes vote down vote up
package fs2

import cats.effect.concurrent.Deferred
import cats.effect.{ ConcurrentEffect, IO }
import fs2.aws.sqs.{ ConsumerBuilder, ReceiverCallback, SqsConfig }
import fs2.concurrent.Queue
import javax.jms.{ Message, MessageListener }

package object aws {

  def sqsStream[F[_], O](
    sqsConfig: SqsConfig,
    builder: (SqsConfig, MessageListener) => ConsumerBuilder[F],
    d: Option[Deferred[F, MessageListener]] = None
  )(implicit F: ConcurrentEffect[F], decoder: Message => Either[Throwable, O]): fs2.Stream[F, O] =
    for {
      q        <- fs2.Stream.eval(Queue.unbounded[F, Either[Throwable, O]])
      callback <- fs2.Stream.eval(callback(q))
      _        <- fs2.Stream.eval(d.map(_.complete(callback)).getOrElse(F.unit))
      item     <- builder(sqsConfig, callback).serve(q.dequeue.rethrow)
    } yield item

  def callback[F[_], O](queue: Queue[F, Either[Throwable, O]])(
    implicit F: ConcurrentEffect[F],
    decoder: Message => Either[Throwable, O]
  ): F[ReceiverCallback[Either[Throwable, O]]] =
    F.delay(
      new ReceiverCallback[Either[Throwable, O]](r =>
        F.runAsync(queue.enqueue1(r))(_ => IO.unit).unsafeRunSync()
      )
    )

} 
Example 69
Source File: SqsSpec.scala    From fs2-aws   with MIT License 5 votes vote down vote up
package fs2.aws

import cats.effect.concurrent.Deferred
import cats.effect.{ ContextShift, IO }
import com.amazon.sqs.javamessaging.SQSConnection
import com.amazon.sqs.javamessaging.message.SQSTextMessage
import eu.timepit.refined.auto._
import fs2.aws
import fs2.aws.sqs.{ ConsumerBuilder, SQSConsumer, SqsConfig }
import javax.jms.{ Message, MessageListener, TextMessage }
import org.mockito.scalatest.MockitoSugar
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.concurrent.ExecutionContext

class SqsSpec extends AsyncFlatSpec with Matchers with MockitoSugar {
  implicit val ec: ExecutionContext             = ExecutionContext.global
  implicit val ioContextShift: ContextShift[IO] = IO.contextShift(ec)
  implicit val messageDecoder: Message => Either[Throwable, Int] = { sqs_msg =>
    val text = sqs_msg.asInstanceOf[TextMessage].getText
    if ("fail" == text) Left(intercept[Exception](()))
    else Right(text.toInt)
  }
  "SQS endpoint" should "stream messages" in {

    def stream(d: Deferred[IO, MessageListener]) =
      aws
        .sqsStream[IO, Int](
          SqsConfig("dummy"),
          (_, listener) =>
            new ConsumerBuilder[IO] {
              override def start: IO[SQSConsumer] =
                IO.delay(new SQSConsumer {
                  override def callback: MessageListener = listener

                  override def startConsumer(): Unit = ()

                  override def shutdown(): Unit = ()

                  override def connection: SQSConnection = mock[SQSConnection]
                })
            },
          Some(d)
        )
        .take(4)
        .compile
        .toList

    val r = for {
      d <- Deferred[IO, MessageListener]
      res <- IO.racePair(stream(d), d.get).flatMap {
              case Right((streamFiber, listener)) =>
                listener.onMessage(new SQSTextMessage("1"))
                listener.onMessage(new SQSTextMessage("2"))
                listener.onMessage(new SQSTextMessage("fail"))
                listener.onMessage(new SQSTextMessage("4"))
                listener.onMessage(new SQSTextMessage("5"))
                streamFiber.join
              case _ => IO(Nil)
            }
    } yield res

    val future = r.unsafeToFuture()

    future.map(_ should be(List(1, 2, 4, 5)))
  }
} 
Example 70
Source File: package.scala    From fs2-aws   with MIT License 5 votes vote down vote up
package fs2.aws

import java.io._

import cats.effect.{ Effect, IO }
import com.amazonaws.SdkClientException
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.model.{ AmazonS3Exception, GetObjectRequest, S3ObjectInputStream }
import fs2.aws.internal._
import org.apache.http.client.methods.HttpRequestBase
import scala.io.Source

package object utils {
  val s3TestClient: S3Client[IO] = new S3Client[IO] {

    override def client: AmazonS3 =
      throw new NotImplementedError("s3 client shouldn't be used in this test client")

    override def getObjectContentOrError(
      getObjectRequest: GetObjectRequest
    )(implicit e: Effect[IO]): IO[Either[Throwable, InputStream]] =
      getObjectRequest match {
        case goe: GetObjectRequest => {
          IO[Either[Throwable, ByteArrayInputStream]] {
            val fileContent: Array[Byte] =
              try {
                Source.fromResource(goe.getKey).mkString.getBytes
              } catch {
                case _: FileNotFoundException => throw new AmazonS3Exception("File not found")
                case e: Throwable             => throw e
              }
            goe.getRange match {
              case Array(x, y) =>
                if (y > fileContent.length)
                  Right(new ByteArrayInputStream(fileContent.slice(x.toInt, fileContent.length)))
                else Right(new ByteArrayInputStream(fileContent.slice(x.toInt, y.toInt)))
            }

          } map {
            case Left(e) => Left(e)
            case Right(is) =>
              Thread.sleep(500) // simulate a call to S3
              Right(new S3ObjectInputStream(is, new HttpRequestBase {
                def getMethod = ""
              }))
          }
        }
        case _ => throw new SdkClientException("Invalid GetObjectRequest")
      }

    override def getObjectContent(
      getObjectRequest: GetObjectRequest
    )(implicit e: Effect[IO]): IO[InputStream] =
      IO[ByteArrayInputStream] {
        val fileContent: Array[Byte] =
          try {
            val testS3Resource = Option(getObjectRequest.getVersionId) match {
              case Some(version) => s"${getObjectRequest.getKey}_v$version"
              case None          => getObjectRequest.getKey
            }
            Source.fromResource(testS3Resource).mkString.getBytes
          } catch {
            case _: FileNotFoundException => throw new AmazonS3Exception("File not found")
            case e: Throwable             => throw e
          }
        new ByteArrayInputStream(fileContent)

      }.map { is =>
        Thread.sleep(500) // simulate a call to S3
        new S3ObjectInputStream(is, new HttpRequestBase {
          def getMethod = ""
        })
      }
  }

} 
Example 71
Source File: S3Spec.scala    From fs2-aws   with MIT License 5 votes vote down vote up
package fs2
package aws

import java.util.concurrent.Executors

import cats.effect.{ ContextShift, IO }
import com.amazonaws.services.s3.AmazonS3
import fs2.aws.internal.S3Client
import fs2.aws.s3._
import org.mockito.MockitoSugar._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.concurrent.ExecutionContext

class S3Spec extends AnyFlatSpec with Matchers {

  private val blockingEC                        = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(6))
  implicit val ec: ExecutionContext             = ExecutionContext.global
  implicit val ioContextShift: ContextShift[IO] = IO.contextShift(ec)

  implicit val s3Client: S3Client[IO] = fs2.aws.utils.s3TestClient
  val mockS3                          = mock[AmazonS3]

  ignore should "stdout the jsonfile" in {
    readS3FileMultipart[IO]("resources", "jsontest.json", 25, mockS3).compile.toVector.unsafeRunSync should be(
      Vector()
    )
  }

  "Downloading the JSON test file by chunks" should "return the same content" in {
    readS3FileMultipart[IO]("resources", "jsontest.json", 25, mockS3)
      .through(fs2.text.utf8Decode)
      .through(fs2.text.lines)
      .compile
      .toVector
      .unsafeRunSync
      .reduce(_ + _)
      .concat("") should be(
      """{"test": 1}{"test": 2}{"test": 3}{"test": 4}{"test": 5}{"test": 6}{"test": 7}{"test": 8}"""
    )
  }

  "Downloading the JSON test file" should "return the same content" in {
    readS3File[IO]("resources", "jsontest.json", blockingEC, mockS3)
      .through(fs2.text.utf8Decode)
      .through(fs2.text.lines)
      .compile
      .toVector
      .unsafeRunSync
      .reduce(_ + _)
      .concat("") should be(
      """{"test": 1}{"test": 2}{"test": 3}{"test": 4}{"test": 5}{"test": 6}{"test": 7}{"test": 8}"""
    )
  }

  "Downloading the versioned JSON test file" should "return the same content" in {
    readS3VersionedFile[IO]("resources", "jsontest.json", version = "ABC", blockingEC, mockS3)
      .through(fs2.text.utf8Decode)
      .through(fs2.text.lines)
      .compile
      .toVector
      .unsafeRunSync
      .reduce(_ + _)
      .concat("") should be(
      """{"this": 1}{"is": 2}{"versioned": 3}{"content": 4}"""
    )
  }

  "big chunk size but small entire text" should "be trimmed to content" in {
    readS3FileMultipart[IO]("resources", "jsontest1.json", 25, mockS3)
      .through(fs2.text.utf8Decode)
      .through(fs2.text.lines)
      .compile
      .toVector
      .unsafeRunSync
      .reduce(_ + _)
      .concat("") should be("""{"test": 1}""")
  }
} 
Example 72
Source File: DynamoEventParserSpec.scala    From fs2-aws   with MIT License 5 votes vote down vote up
package fs2.aws.dynamodb.parsers

import java.util

import cats.effect.IO
import com.amazonaws.services.dynamodbv2.model.{
  AttributeValue,
  OperationType,
  Record,
  StreamRecord,
  StreamViewType
}
import com.amazonaws.services.dynamodbv2.streamsadapter.model.RecordAdapter
import io.circe.Json
import org.scalatest.wordspec.AnyWordSpec
import io.github.howardjohn.scanamo.CirceDynamoFormat._
import org.scalatest.matchers.should.Matchers

class DynamoEventParserSpec extends AnyWordSpec with Matchers {
  "Dynamo Event Parser" should {
    "parse insert event type" in {
      val sr = new StreamRecord()
      sr.setStreamViewType(StreamViewType.NEW_IMAGE)
      val newImage = new util.HashMap[String, AttributeValue]()
      newImage.put("name", new AttributeValue().withS("Barry"))
      sr.setNewImage(newImage)
      val r = new Record()
      r.setEventName(OperationType.INSERT)
      r.withDynamodb(sr)
      parseDynamoEvent[IO, Json](new RecordAdapter(r)).unsafeRunSync() should be(
        Insert(Json.obj("name" -> Json.fromString("Barry")))
      )
    }

    "parse modify event type" in {
      val sr = new StreamRecord()
      sr.setStreamViewType(StreamViewType.NEW_AND_OLD_IMAGES)
      val oldImage = new util.HashMap[String, AttributeValue]()
      oldImage.put("name", new AttributeValue().withS("Dmytro"))
      sr.setOldImage(oldImage)
      val newImage = new util.HashMap[String, AttributeValue]()
      newImage.put("name", new AttributeValue().withS("Barry"))
      sr.setNewImage(newImage)
      val r = new Record()
      r.setEventName(OperationType.MODIFY)
      r.withDynamodb(sr)
      parseDynamoEvent[IO, Json](new RecordAdapter(r)).unsafeRunSync() should be(
        Update(
          Json.obj("name" -> Json.fromString("Dmytro")),
          Json.obj("name" -> Json.fromString("Barry"))
        )
      )
    }

    "parse delete event type" in {
      val sr = new StreamRecord()
      sr.setStreamViewType(StreamViewType.NEW_AND_OLD_IMAGES)
      val oldImage = new util.HashMap[String, AttributeValue]()
      oldImage.put("name", new AttributeValue().withS("Dmytro"))
      sr.setOldImage(oldImage)
      val r = new Record()
      r.setEventName(OperationType.REMOVE)
      r.withDynamodb(sr)
      parseDynamoEvent[IO, Json](new RecordAdapter(r)).unsafeRunSync() should be(
        Delete(
          Json.obj("name" -> Json.fromString("Dmytro"))
        )
      )
    }
    "parse modify event type with NewImage view only as Insert" in {
      val sr = new StreamRecord()
      sr.setStreamViewType(StreamViewType.NEW_IMAGE)
      val newImage = new util.HashMap[String, AttributeValue]()
      newImage.put("name", new AttributeValue().withS("Barry"))
      sr.setNewImage(newImage)
      val r = new Record()
      r.setEventName(OperationType.MODIFY)
      r.withDynamodb(sr)
      parseDynamoEvent[IO, Json](new RecordAdapter(r)).unsafeRunSync() should be(
        Insert(Json.obj("name" -> Json.fromString("Barry")))
      )
    }

    "do not support NEW_IMAGE view type with REMOVE operation type" in {
      val sr = new StreamRecord()
      sr.setStreamViewType(StreamViewType.NEW_IMAGE)
      val oldImage = new util.HashMap[String, AttributeValue]()
      oldImage.put("name", new AttributeValue().withS("Barry"))
      sr.setNewImage(oldImage)
      val r = new Record()
      r.setEventName(OperationType.REMOVE)
      r.withDynamodb(sr)
      parseDynamoEvent[IO, Json](new RecordAdapter(r)).unsafeRunSync() should be(
        Unsupported("NEW_IMAGE is not supported with REMOVE")
      )
    }
  }
} 
Example 73
Source File: CatsHelpers.scala    From nelson   with Apache License 2.0 5 votes vote down vote up
package nelson

import cats.Eval
import cats.effect.{Effect, IO, Timer}
import cats.free.Cofree
import cats.syntax.functor._
import cats.syntax.monadError._

import fs2.{Pipe, Sink, Stream}

import quiver.{Context, Decomp, Graph}

import java.util.concurrent.TimeoutException

import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration
import scala.collection.immutable.{Stream => SStream}

object CatsHelpers {
  implicit class NelsonEnrichedIO[A](val io: IO[A]) extends AnyVal {
    
  private type Tree[A] = Cofree[SStream, A]

  private def flattenTree[A](tree: Tree[A]): SStream[A] = {
    def go(tree: Tree[A], xs: SStream[A]): SStream[A] =
      SStream.cons(tree.head, tree.tail.value.foldRight(xs)(go(_, _)))
    go(tree, SStream.Empty)
  }

  private def Node[A](root: A, forest: => SStream[Tree[A]]): Tree[A] =
    Cofree[SStream, A](root, Eval.later(forest))

  implicit class NelsonEnrichedGraph[N, A, B](val graph: Graph[N, A, B]) extends AnyVal {
    def reachable(v: N): Vector[N] =
      xdfWith(Seq(v), _.successors, _.vertex)._1.flatMap(flattenTree)

    def xdfWith[C](vs: Seq[N], d: Context[N, A, B] => Seq[N], f: Context[N, A, B] => C): (Vector[Tree[C]], Graph[N, A, B]) =
      if (vs.isEmpty || graph.isEmpty) (Vector(), graph)
      else graph.decomp(vs.head) match {
        case Decomp(None, g) => g.xdfWith(vs.tail, d, f)
        case Decomp(Some(c), g) =>
          val (xs, _) = g.xdfWith(d(c), d, f)
          val (ys, g3) = g.xdfWith(vs.tail, d, f)
          (Node(f(c), xs.toStream) +: ys, g3)
      }
  }
} 
Example 74
Source File: KubernetesHealthClient.scala    From nelson   with Apache License 2.0 5 votes vote down vote up
package nelson
package health

import nelson.health.HealthCheckOp._

import nelson.CatsHelpers._
import cats.~>
import cats.effect.IO

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.ExecutionContext

final class KubernetesHealthClient(
  kubectl: Kubectl,
  timeout: FiniteDuration,
  executionContext: ExecutionContext
) extends (HealthCheckOp ~> IO) {
  private implicit val kubernetesShellExecutionContext = executionContext

  def apply[A](fa: HealthCheckOp[A]): IO[A] = fa match {
    case Health(_, ns, stackName) => kubectl.getPods(ns, stackName).timed(timeout)
  }
} 
Example 75
Source File: KubernetesShell.scala    From nelson   with Apache License 2.0 5 votes vote down vote up
package nelson
package scheduler

import nelson.Datacenter.{Deployment, StackName}
import nelson.Kubectl.{DeploymentStatus, JobStatus, KubectlError}
import nelson.scheduler.SchedulerOp._

import nelson.CatsHelpers._
import cats.~>
import cats.effect.IO
import cats.implicits._

import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration
import java.util.concurrent.ScheduledExecutorService


final class KubernetesShell(
  kubectl: Kubectl,
  timeout: FiniteDuration,
  scheduler: ScheduledExecutorService,
  executionContext: ExecutionContext
) extends (SchedulerOp ~> IO) {
  import KubernetesShell._

  private implicit val kubernetesShellExecutionContext = executionContext

  def apply[A](fa: SchedulerOp[A]): IO[A] = fa match {
    case Delete(_, deployment) =>
      delete(deployment)
        .retryExponentially(limit = 3)(scheduler, kubernetesShellExecutionContext)
        .timed(timeout)
    case Launch(_, _, _, _, _, _, bp) =>
      kubectl.apply(bp)
        .retryExponentially(limit = 3)(scheduler, kubernetesShellExecutionContext)
        .timed(timeout)
    case Summary(_, ns, stackName) =>
      summary(ns, stackName)
        .retryExponentially(limit = 3)(scheduler, kubernetesShellExecutionContext)
        .timed(timeout)
  }

  def delete(deployment: Deployment): IO[Unit] = {
    val ns = deployment.namespace.name
    val stack = deployment.stackName

    // We don't have enough information here to determine what exactly
    // we're trying to delete so try each one in turn..
    val fallback =
      kubectl.deleteService(ns, stack).void.recoverWith {
        case err@KubectlError(_) if notFound(err) => kubectl.deleteCronJob(ns, stack).void.recoverWith {
          case err@KubectlError(_) if notFound(err) => kubectl.deleteJob(ns, stack).void.recover {
            case err@KubectlError(_) if notFound(err) => ()
          }
        }
      }

    deployment.renderedBlueprint.fold(fallback)(spec => kubectl.delete(spec).void)
  }

  // Janky heuristic to see if an attempted (legacy) deletion failed because
  // it was not found as opposed to some other reason like RBAC permissions
  private def notFound(error: KubectlError): Boolean =
    error.stderr.exists(_.startsWith("Error from server (NotFound)"))

  def summary(ns: NamespaceName, stackName: StackName): IO[Option[DeploymentSummary]] =
    deploymentSummary(ns, stackName).recoverWith { case _ =>
      cronJobSummary(ns, stackName).recoverWith { case _ =>
        jobSummary(ns, stackName).recover { case _ => None }
      }
    }

  def deploymentSummary(ns: NamespaceName, stackName: StackName): IO[Option[DeploymentSummary]] =
    kubectl.getDeployment(ns, stackName).map {
      case DeploymentStatus(available, unavailable) =>
        Some(DeploymentSummary(
          running = available,
          pending = unavailable,
          completed = None,
          failed = None
        ))
    }

  def cronJobSummary(ns: NamespaceName, stackName: StackName): IO[Option[DeploymentSummary]] =
    kubectl.getCronJob(ns, stackName).map(js => Some(jobStatusToSummary(js)))

  def jobSummary(ns: NamespaceName, stackName: StackName): IO[Option[DeploymentSummary]] =
    kubectl.getJob(ns, stackName).map(js => Some(jobStatusToSummary(js)))
}

object KubernetesShell {
  private def jobStatusToSummary(js: JobStatus): DeploymentSummary =
    DeploymentSummary(
      running   = js.active,
      pending   = None,         // Doesn't seem like K8s API gives this info
      completed = js.succeeded,
      failed    = js.failed
    )
} 
Example 76
Source File: StubbedConsulClient.scala    From nelson   with Apache License 2.0 5 votes vote down vote up
package nelson

import cats.~>
import cats.effect.IO

import helm.ConsulOp

import scala.collection.immutable.Set

object StubbedConsulClient extends (ConsulOp ~> IO) {
  def apply[A](fa: ConsulOp[A]): IO[A] = fa match {
    case ConsulOp.KVGet(_) => IO.pure(None)
    case ConsulOp.KVSet(_, _) => IO.unit
    case ConsulOp.KVListKeys(_) => IO.pure(Set.empty)
    case ConsulOp.KVDelete(_) => IO.unit
    case ConsulOp.HealthListChecksForService(_, _, _, _) => IO.pure(List.empty)
    case ConsulOp.HealthListChecksForNode(_, _) => IO.pure(List.empty)
    case ConsulOp.HealthListChecksInState(_, _, _, _) => IO.pure(List.empty)
    case ConsulOp.HealthListNodesForService(_, _, _, _, _, _) => IO.pure(List.empty)
    case ConsulOp.AgentRegisterService(_, _, _, _, _, _, _, _) => IO.unit
    case ConsulOp.AgentDeregisterService(_) => IO.unit
    case ConsulOp.AgentListServices => IO.pure(Map.empty)
    case ConsulOp.AgentEnableMaintenanceMode(_, _, _) => IO.unit
  }
} 
Example 77
Source File: DefaultBlueprints.scala    From nelson   with Apache License 2.0 5 votes vote down vote up
package nelson
package blueprint

import cats.effect.IO

import fs2.{io, text}

object DefaultBlueprints {
  private def templateFromClasspath(path: String): IO[Template] =
    io.readInputStream(IO(getClass.getClassLoader.getResourceAsStream(path)), 4096)
      .through(text.utf8Decode)
      .through(text.lines)
      .compile
      .toList
      .map(lines => Template.load(s"nelson-default-${path}", lines.mkString("\n")))

  object canopus {
    val service = templateFromClasspath("nelson/canopus_service.mustache")
    val cronJob = templateFromClasspath("nelson/canopus_cron_job.mustache")
    val job = templateFromClasspath("nelson/canopus_job.mustache")
  }
} 
Example 78
Source File: hikari.scala    From nelson   with Apache License 2.0 5 votes vote down vote up
package nelson
package storage

import doobie.hikari._
import cats.effect.IO

object Hikari {

  def build(db: DatabaseConfig): HikariTransactor[IO] = {
    val trans = for {
      xa <- HikariTransactor.newHikariTransactor[IO](db.driver, db.connection, db.username.getOrElse(""), db.password.getOrElse(""))
       _ <- xa.configure(hx => IO(db.maxConnections.foreach(max => hx.setMaximumPoolSize(max))))
    } yield xa

    trans.unsafeRunSync()
  }
} 
Example 79
Source File: Migrate.scala    From nelson   with Apache License 2.0 5 votes vote down vote up
package nelson
package storage

import cats.effect.IO
import org.flywaydb.core.Flyway
import journal.Logger

object Migrate {

  val log = Logger[Migrate.type]

  def migrate(cfg: DatabaseConfig): IO[Unit] =
    IO {
      val flyway = new Flyway
      flyway.setDataSource(
        cfg.connection,
        cfg.username.getOrElse(""),
        cfg.password.getOrElse(""))

      try {
        log.info("Conducting database schema migrations if needed.")
        val completed = flyway.migrate()
        log.info(s"Completed $completed succsessful migrations.")
      } catch {
        case e: Throwable => {
          // attempt a repair (useful for local debugging)
          log.error(s"Failed to migrate database. ${e.getMessage}")
          log.info("Repairing database before retrying migration")
          flyway.repair()
          val completed = flyway.migrate()
          log.info(s"After repair, completed $completed succsessful migrations.")
        }
      }
    }
} 
Example 80
Source File: Math2.scala    From skunk   with MIT License 5 votes vote down vote up
// Copyright (c) 2018-2020 by Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package example

import cats.Monad
import cats.effect.{ ExitCode, IO, IOApp, Resource }
import skunk.Session
import skunk.implicits._
import skunk.codec.numeric.{ int4, float8 }
import natchez.Trace.Implicits.noop

object Math2 extends IOApp {

  val session: Resource[IO, Session[IO]] =
    Session.single(
      host     = "localhost",
      port     = 5432,
      user     = "jimmy",
      database = "world",
      password = Some("banana"),
      debug    = true
    )

  // An algebra for doing math.
  trait Math[F[_]] {
    def add(a: Int, b: Int): F[Int]
    def sqrt(d: Double): F[Double]
  }

  object Math {

    object Statements {
      val add  = sql"select $int4 + $int4".query(int4)
      val sqrt = sql"select sqrt($float8)".query(float8)
    }

    def fromSession[F[_]: Monad](sess: Session[F]): Resource[F, Math[F]] =
      for {
        pAdd  <- sess.prepare(Statements.add)
        pSqrt <- sess.prepare(Statements.sqrt)
      } yield
        new Math[F] {
          def add(a: Int, b: Int) = pAdd.unique(a ~ b)
          def sqrt(d: Double)     = pSqrt.unique(d)
        }

  }

  def run(args: List[String]): IO[ExitCode] =
    session.flatMap(Math.fromSession(_)).use { m =>
      for {
        n  <- m.add(42, 71)
        d  <- m.sqrt(2)
        d2 <- m.sqrt(42)
        _  <- IO(println(s"The answers were $n and $d and $d2"))
      } yield ExitCode.Success
    }

} 
Example 81
Source File: Math1.scala    From skunk   with MIT License 5 votes vote down vote up
// Copyright (c) 2018-2020 by Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package example

import cats.effect.{ Bracket, ExitCode, IO, IOApp, Resource }
import skunk.Session
import skunk.implicits._
import skunk.codec.numeric.{ int4, float8 }
import natchez.Trace.Implicits.noop

object Math1 extends IOApp {

  val session: Resource[IO, Session[IO]] =
    Session.single(
      host     = "localhost",
      port     = 5432,
      user     = "jimmy",
      database = "world",
      password = Some("banana")
    )

  // An algebra for doing math.
  trait Math[F[_]] {
    def add(a: Int, b: Int): F[Int]
    def sqrt(d: Double): F[Double]
  }

  object Math {

    object Statements {
      val add  = sql"select $int4 + $int4".query(int4)
      val sqrt = sql"select sqrt($float8)".query(float8)
    }

    // `Math` implementation that delegates its work to Postgres.
    def fromSession[F[_]: Bracket[?[_], Throwable]](sess: Session[F]): Math[F] =
      new Math[F] {
        def add(a: Int, b: Int) = sess.prepare(Statements.add).use(_.unique(a ~ b))
        def sqrt(d: Double)     = sess.prepare(Statements.sqrt).use(_.unique(d))
      }

  }

  def run(args: List[String]): IO[ExitCode] =
    session.map(Math.fromSession(_)).use { m =>
      for {
        n  <- m.add(42, 71)
        d  <- m.sqrt(2)
        d2 <- m.sqrt(42)
        _  <- IO(println(s"The answers were $n and $d and $d2"))
      } yield ExitCode.Success
    }

} 
Example 82
Source File: SkunkTest.scala    From skunk   with MIT License 5 votes vote down vote up
// Copyright (c) 2018-2020 by Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package tests

import cats.effect.{ IO, Resource }
import cats.implicits._
import skunk.Session
import skunk.data._
import skunk.codec.all._
import skunk.implicits._
import skunk.util.Typer
import natchez.Trace.Implicits.noop

abstract class SkunkTest(debug: Boolean = false, strategy: Typer.Strategy = Typer.Strategy.BuiltinsOnly) extends ffstest.FTest {

  val session: Resource[IO, Session[IO]] =
    Session.single(
      host     = "localhost",
      port     = 5432,
      user     = "jimmy",
      database = "world",
      password = Some("banana"),
      strategy = strategy,
      debug    = debug
    )

  def sessionTest[A](name: String)(fa: Session[IO] => IO[A]): Unit =
    test(name)(session.use(fa))

  implicit class SkunkTestSessionOps(s: Session[IO]) {

    def assertTransactionStatus(msg: String, xas: TransactionStatus): IO[Unit] =
      s.transactionStatus.get.flatMap(a => assert(s"$msg (expected $xas, got $a)", a === xas))

    def assertHealthy: IO[Unit] =
      for {
        _ <- assertTransactionStatus("sanity check", TransactionStatus.Idle)
        n <- s.unique(sql"select 'SkunkTest Health Check'::varchar".query(varchar))
        _ <- assert("sanity check", n == "SkunkTest Health Check")
      } yield ()

  }

} 
Example 83
Source File: ErrorResponseTest.scala    From skunk   with MIT License 5 votes vote down vote up
// Copyright (c) 2018-2020 by Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package tests

import cats.effect.IO
import skunk.codec.all._
import skunk.exception.PostgresErrorException
import skunk.implicits._


case object ErrorResponseTest extends SkunkTest {

  sessionTest("simple command, syntax error") { s =>
    for {
      _ <- s.execute(sql"foo?".command).assertFailsWith[PostgresErrorException]
      _ <- s.assertHealthy
    } yield "ok"
  }

  sessionTest("simple query, syntax error") { s =>
    for {
      _ <- s.execute(sql"foo?".query(int4)).assertFailsWith[PostgresErrorException]
      _ <- s.assertHealthy
    } yield "ok"
  }

  sessionTest("prepared query, syntax error") { s =>
    for {
      _ <- s.prepare(sql"foo?".query(int4)).use(_ => IO.unit).assertFailsWith[PostgresErrorException]
      _ <- s.assertHealthy
    } yield "ok"
  }

  sessionTest("prepared command, syntax error") { s =>
    for {
      _ <- s.prepare(sql"foo?".command).use(_ => IO.unit).assertFailsWith[PostgresErrorException]
      _ <- s.assertHealthy
    } yield "ok"
  }

  // test("prepared query, bind error") {
  //   fail("Not implemented.")
  // }

  // test("prepared query, bind error") {
  //   fail("Not implemented.")
  // }

} 
Example 84
Source File: Integration.scala    From helm   with Apache License 2.0 5 votes vote down vote up
package helm
package http4s

import scala.concurrent.duration.DurationInt

import cats.effect.IO
import cats.implicits._
import com.github.dockerjava.core.DefaultDockerClientConfig
import com.github.dockerjava.netty.NettyDockerCmdExecFactory
import com.whisk.docker._
import com.whisk.docker.impl.dockerjava.{Docker, DockerJavaExecutorFactory}
import com.whisk.docker.scalatest._
import journal.Logger
import org.http4s._
import org.http4s.client.blaze._
import org.scalacheck._
import org.scalatest._
import org.scalatest.enablers.CheckerAsserting
import org.scalatest.prop._

// This is how we use docker-kit.  Nothing specific to helm in this trait.
trait DefaultDockerKit extends DockerKit {
  override implicit val dockerFactory: DockerFactory = new DockerJavaExecutorFactory(
    new Docker(DefaultDockerClientConfig.createDefaultConfigBuilder().build(),
      factory = new NettyDockerCmdExecFactory()))

  
  lazy val dockerHost: String = {
    // i'm expecting protocol://ip:port
    sys.env.get("DOCKER_HOST").flatMap { url =>
      val parts = url.split(":")
      if (parts.length == 3)
        Some(parts(1).substring(2))
      else None
    }.getOrElse("127.0.0.1")
  }
}

trait DockerConsulService extends DefaultDockerKit {
  private[this] val logger = Logger[DockerConsulService]

  override implicit val dockerFactory: DockerFactory = new DockerJavaExecutorFactory(
    new Docker(DefaultDockerClientConfig.createDefaultConfigBuilder().build(),
      factory = new NettyDockerCmdExecFactory()))

  val ConsulPort = 18512

  val consulContainer =
    DockerContainer("consul:0.7.0", name = Some("consul"))
      .withPorts(8500 -> Some(ConsulPort))
      .withLogLineReceiver(LogLineReceiver(true, s => logger.debug(s"consul: $s")))
      .withReadyChecker(DockerReadyChecker.LogLineContains("agent: Synced"))

  abstract override def dockerContainers: List[DockerContainer] =
    consulContainer :: super.dockerContainers
}

class IntegrationSpec
    extends FlatSpec
    with Matchers
    with Checkers
    with BeforeAndAfterAll
    with DockerConsulService with DockerTestKit {

  val client = Http1Client[IO]().unsafeRunSync

  val baseUrl: Uri =
    Uri.fromString(s"http://${dockerHost}:${ConsulPort}").valueOr(throw _)

  val interpreter = new Http4sConsulClient(baseUrl, client)

  "consul" should "work" in check { (k: String, v: Array[Byte]) =>
    scala.concurrent.Await.result(dockerContainers.head.isReady(), 20.seconds)
    helm.run(interpreter, ConsulOp.kvSet(k, v)).unsafeRunSync
    // Equality comparison for Option[Array[Byte]] doesn't work properly. Since we expect the value to always be Some making a custom matcher doesn't seem worthwhile, so call .get on the Option
    // See https://github.com/scalatest/scalatest/issues/491
    helm.run(interpreter, ConsulOp.kvGetRaw(k, None, None)).unsafeRunSync.value.get should be (v)

    helm.run(interpreter, ConsulOp.kvListKeys("")).unsafeRunSync should contain (k)
    helm.run(interpreter, ConsulOp.kvDelete(k)).unsafeRunSync
    helm.run(interpreter, ConsulOp.kvListKeys("")).unsafeRunSync should not contain (k)
    true
  }(implicitly, implicitly, Arbitrary(Gen.alphaStr suchThat(_.size > 0)), implicitly, implicitly, Arbitrary.arbContainer[Array, Byte], implicitly, implicitly, implicitly[CheckerAsserting[EntityDecoder[IO, Array[Byte]]]], implicitly, implicitly)
} 
Example 85
Source File: ConsulOpTests.scala    From helm   with Apache License 2.0 5 votes vote down vote up
package helm

import argonaut._, Argonaut._
import cats.effect.IO
import org.scalatest.{FlatSpec, Matchers}
import org.scalactic.TypeCheckedTripleEquals
import ConsulOp._

class ConsulOpTests extends FlatSpec with Matchers with TypeCheckedTripleEquals {
  val I = Interpreter.prepare[ConsulOp, IO]

  "getJson" should "return none right when get returns None" in {
    val interp = for {
      _ <- I.expectU[QueryResponse[Option[Array[Byte]]]] {
        case ConsulOp.KVGetRaw("foo", None, None) => IO.pure(QueryResponse(None, -1, true, -1))
      }
    } yield ()
    interp.run(kvGetJson[Json]("foo", None, None)).unsafeRunSync should equal(Right(QueryResponse(None, -1, true, -1)))
  }

  it should "return a value when get returns a decodeable value" in {
    val interp = for {
      _ <- I.expectU[QueryResponse[Option[Array[Byte]]]] {
        case ConsulOp.KVGetRaw("foo", None, None) => IO.pure(QueryResponse(Some("42".getBytes), -1, true, -1))
      }
    } yield ()
    interp.run(kvGetJson[Json]("foo", None, None)).unsafeRunSync should equal(Right(QueryResponse(Some(jNumber(42)), -1, true, -1)))
  }

  it should "return an error when get returns a non-decodeable value" in {
    val interp = for {
      _ <- I.expectU[QueryResponse[Option[Array[Byte]]]] {
        case ConsulOp.KVGetRaw("foo", None, None) => IO.pure(QueryResponse(Some("{".getBytes), -1, true, -1))
      }
    } yield ()
    interp.run(kvGetJson[Json]("foo", None, None)).unsafeRunSync should equal(Left("JSON terminates unexpectedly."))
  }
} 
Example 86
Source File: AuthQueryTypeCheckSpec.scala    From scala-pet-store   with Apache License 2.0 5 votes vote down vote up
package io.github.pauljamescleary.petstore
package infrastructure.repository.doobie

import cats.effect.IO
import doobie.scalatest.IOChecker
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import PetStoreArbitraries._
import tsec.mac.jca.HMACSHA256
import tsec.authentication.AugmentedJWT
import tsec.common.SecureRandomId
import org.scalatest.matchers.should.Matchers

class AuthQueryTypeCheckSpec
    extends AnyFunSuite
    with Matchers
    with ScalaCheckPropertyChecks
    with IOChecker {
  override def transactor: doobie.Transactor[IO] = testTransactor

  import AuthSQL._

  test("Typecheck auth queries") {
    forAll { jwt: AugmentedJWT[HMACSHA256, Long] => check(insert(jwt)) }
    forAll { jwt: AugmentedJWT[HMACSHA256, Long] => check(update(jwt)) }
    forAll { id: SecureRandomId => check(select(id)) }
    forAll { id: SecureRandomId => check(delete(id)) }
  }
} 
Example 87
Source File: PetQueryTypeCheckSpec.scala    From scala-pet-store   with Apache License 2.0 5 votes vote down vote up
package io.github.pauljamescleary.petstore
package infrastructure.repository.doobie

import cats.data.NonEmptyList
import cats.effect.IO
import cats.syntax.applicative._
import doobie.scalatest.IOChecker
import doobie.util.transactor.Transactor
import org.scalatest.funsuite.AnyFunSuite
import PetStoreArbitraries.pet
import org.scalatest.matchers.should.Matchers

class PetQueryTypeCheckSpec extends AnyFunSuite with Matchers with IOChecker {
  override val transactor: Transactor[IO] = testTransactor

  import PetSQL._

  test("Typecheck pet queries") {
    pet.arbitrary.sample.map { p =>
      check(selectByStatus(p.status.pure[NonEmptyList]))
      check(insert(p))
      p.id.foreach(id => check(PetSQL.update(p, id)))
    }

    check(selectTagLikeString("example".pure[NonEmptyList]))
    check(select(1L))
    check(selectAll)
    check(delete(1L))
    check(selectByNameAndCategory("name", "category"))
  }
} 
Example 88
Source File: UserQueryTypeCheckSpec.scala    From scala-pet-store   with Apache License 2.0 5 votes vote down vote up
package io.github.pauljamescleary.petstore
package infrastructure.repository.doobie

import org.scalatest.funsuite.AnyFunSuite
import cats.effect.IO
import doobie.scalatest.IOChecker
import doobie.util.transactor.Transactor

import PetStoreArbitraries.user
import org.scalatest.matchers.should.Matchers

class UserQueryTypeCheckSpec extends AnyFunSuite with Matchers with IOChecker {
  override val transactor: Transactor[IO] = testTransactor

  import UserSQL._

  test("Typecheck user queries") {
    user.arbitrary.sample.map { u =>
      check(insert(u))
      check(byUserName(u.userName))
      u.id.foreach(id => check(update(u, id)))
    }
    check(selectAll)
    check(select(1L))
    check(delete(1L))
  }
} 
Example 89
Source File: package.scala    From scala-pet-store   with Apache License 2.0 5 votes vote down vote up
package io.github.pauljamescleary.petstore
package infrastructure.repository

import cats.implicits._
import cats.effect.{Async, ContextShift, Effect, IO}
import config._
import _root_.doobie.Transactor
import io.circe.config.parser

import scala.concurrent.ExecutionContext

package object doobie {
  def getTransactor[F[_]: Async: ContextShift](cfg: DatabaseConfig): Transactor[F] =
    Transactor.fromDriverManager[F](
      cfg.driver, // driver classname
      cfg.url, // connect URL (driver-specific)
      cfg.user, // user
      cfg.password, // password
    )

  
  def initializedTransactor[F[_]: Effect: Async: ContextShift]: F[Transactor[F]] =
    for {
      petConfig <- parser.decodePathF[F, PetStoreConfig]("petstore")
      _ <- DatabaseConfig.initializeDb(petConfig.db)
    } yield getTransactor(petConfig.db)

  lazy val testEc = ExecutionContext.Implicits.global

  implicit lazy val testCs = IO.contextShift(testEc)

  lazy val testTransactor = initializedTransactor[IO].unsafeRunSync()
} 
Example 90
Source File: LoginTest.scala    From scala-pet-store   with Apache License 2.0 5 votes vote down vote up
package io.github.pauljamescleary.petstore
package infrastructure.endpoint

import cats.data.Kleisli
import cats.effect.IO
import domain.authentication.{LoginRequest, SignupRequest}
import domain.users.{Role, User}
import org.http4s.circe.{jsonEncoderOf, jsonOf}
import org.http4s.client.dsl.Http4sClientDsl
import org.http4s.{EntityDecoder, EntityEncoder, HttpApp, Request, Response}
import org.http4s.implicits._
import org.http4s.headers.Authorization
import io.circe.generic.auto._
import org.http4s.dsl.Http4sDsl

trait LoginTest extends Http4sClientDsl[IO] with Http4sDsl[IO] {
  implicit val userEnc: EntityEncoder[IO, User] = jsonEncoderOf
  implicit val userDec: EntityDecoder[IO, User] = jsonOf

  implicit val signUpRequestEnc: EntityEncoder[IO, SignupRequest] = jsonEncoderOf
  implicit val signUpRequestDec: EntityDecoder[IO, SignupRequest] = jsonOf

  implicit val loginRequestEnc: EntityEncoder[IO, LoginRequest] = jsonEncoderOf
  implicit val loginRequestDec: EntityDecoder[IO, LoginRequest] = jsonOf

  def signUpAndLogIn(
      userSignUp: SignupRequest,
      userEndpoint: HttpApp[IO],
  ): IO[(User, Option[Authorization])] =
    for {
      signUpRq <- POST(userSignUp, uri"/users")
      signUpResp <- userEndpoint.run(signUpRq)
      user <- signUpResp.as[User]
      loginBody = LoginRequest(userSignUp.userName, userSignUp.password)
      loginRq <- POST(loginBody, uri"/users/login")
      loginResp <- userEndpoint.run(loginRq)
    } yield {
      user -> loginResp.headers.get(Authorization)
    }

  def signUpAndLogInAsAdmin(
      userSignUp: SignupRequest,
      userEndpoint: Kleisli[IO, Request[IO], Response[IO]],
  ): IO[(User, Option[Authorization])] =
    signUpAndLogIn(userSignUp.copy(role = Role.Admin), userEndpoint)

  def signUpAndLogInAsCustomer(
      userSignUp: SignupRequest,
      userEndpoint: Kleisli[IO, Request[IO], Response[IO]],
  ): IO[(User, Option[Authorization])] =
    signUpAndLogIn(userSignUp.copy(role = Role.Customer), userEndpoint)
} 
Example 91
Source File: AsyncHttpClientLowLevelFs2WebsocketTest.scala    From sttp   with Apache License 2.0 5 votes vote down vote up
package sttp.client.asynchttpclient.fs2

import cats.effect.IO
import org.asynchttpclient.ws.{WebSocketListener, WebSocket => AHCWebSocket}
import sttp.client._
import sttp.client.asynchttpclient.WebSocketHandler
import sttp.client.impl.cats.CatsTestBase
import sttp.client.testing.websocket.LowLevelListenerWebSocketTest

class AsyncHttpClientLowLevelFs2WebsocketTest
    extends LowLevelListenerWebSocketTest[IO, AHCWebSocket, WebSocketHandler]
    with CatsTestBase {
  implicit val backend: SttpBackend[IO, Nothing, WebSocketHandler] = AsyncHttpClientFs2Backend[IO]().unsafeRunSync()

  override def createHandler(_onTextFrame: String => Unit): WebSocketHandler[AHCWebSocket] =
    WebSocketHandler.fromListener(new WebSocketListener {
      override def onOpen(websocket: AHCWebSocket): Unit = {}
      override def onClose(websocket: AHCWebSocket, code: Int, reason: String): Unit = {}
      override def onError(t: Throwable): Unit = {}
      override def onTextFrame(payload: String, finalFragment: Boolean, rsv: Int): Unit = {
        _onTextFrame(payload)
      }
    })

  override def sendText(ws: AHCWebSocket, t: String): Unit = ws.sendTextFrame(t).await()

  override def sendCloseFrame(ws: AHCWebSocket): Unit = ws.sendCloseFrame()
} 
Example 92
Source File: AsyncHttpClientPipedFs2WebsocketsTest.scala    From sttp   with Apache License 2.0 5 votes vote down vote up
package sttp.client.asynchttpclient.fs2

import cats.effect.concurrent.Ref
import cats.effect.IO
import cats.implicits._
import fs2._
import sttp.client._
import sttp.client.asynchttpclient.WebSocketHandler
import sttp.client.impl.cats.CatsTestBase
import sttp.client.impl.fs2.Fs2WebSockets
import sttp.client.testing.ToFutureWrapper
import sttp.client.ws.WebSocket
import sttp.model.ws.WebSocketFrame
import sttp.client.testing.HttpTest.wsEndpoint

import scala.collection.immutable.Queue
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers

class AsyncHttpClientPipedFs2WebsocketsTest extends AsyncFlatSpec with Matchers with ToFutureWrapper with CatsTestBase {
  implicit val backend: SttpBackend[IO, Nothing, WebSocketHandler] = AsyncHttpClientFs2Backend[IO]().unsafeRunSync()

  def createHandler: Option[Int] => IO[WebSocketHandler[WebSocket[IO]]] = Fs2WebSocketHandler[IO](_)

  it should "run a simple echo pipe" in {
    basicRequest
      .get(uri"$wsEndpoint/ws/echo")
      .openWebsocketF(createHandler(None))
      .product(Ref.of[IO, Queue[String]](Queue.empty))
      .flatMap {
        case (response, results) =>
          Fs2WebSockets.handleSocketThroughTextPipe(response.result) { in =>
            val receive = in.evalMap(m => results.update(_.enqueue(m)))
            val send = Stream("Message 1".asRight, "Message 2".asRight, WebSocketFrame.close.asLeft)
            send merge receive.drain
          } >> results.get.map(_ should contain theSameElementsInOrderAs List("echo: Message 1", "echo: Message 2"))
      }
      .toFuture()
  }

  it should "run a simple read-only client" in {
    basicRequest
      .get(uri"$wsEndpoint/ws/send_and_wait")
      .openWebsocketF(createHandler(None))
      .product(Ref.of[IO, Queue[String]](Queue.empty))
      .flatMap {
        case (response, results) =>
          Fs2WebSockets.handleSocketThroughTextPipe(response.result) { in =>
            in.evalMap(m => results.update(_.enqueue(m)).flatMap(_ => results.get.map(_.size))).flatMap {
              case 2 => Stream(None) // terminating the stream
              case _ => Stream.empty // waiting for more messages
            }.unNoneTerminate
          } >> results.get.map(_ should contain theSameElementsInOrderAs List("test10", "test20"))
      }
      .toFuture()
  }
} 
Example 93
Source File: AsyncHttpClientHighLevelFs2WebsocketTest.scala    From sttp   with Apache License 2.0 5 votes vote down vote up
package sttp.client.asynchttpclient.fs2

import cats.effect.IO
import sttp.client._
import sttp.client.asynchttpclient.{AsyncHttpClientHighLevelWebsocketTest, WebSocketHandler}
import sttp.client.impl.cats.CatsTestBase
import sttp.client.ws.WebSocket

import scala.concurrent.duration._
import cats.implicits._

class AsyncHttpClientHighLevelFs2WebsocketTest extends AsyncHttpClientHighLevelWebsocketTest[IO] with CatsTestBase {
  implicit val backend: SttpBackend[IO, Nothing, WebSocketHandler] = AsyncHttpClientFs2Backend[IO]().unsafeRunSync()

  override def createHandler: Option[Int] => IO[WebSocketHandler[WebSocket[IO]]] = Fs2WebSocketHandler[IO](_)

  override def eventually[T](interval: FiniteDuration, attempts: Int)(f: => IO[T]): IO[T] = {
    def tryWithCounter(i: Int): IO[T] = {
      (IO.sleep(interval) >> f).recoverWith {
        case _: Exception if i < attempts => tryWithCounter(i + 1)
      }
    }
    tryWithCounter(0)
  }
} 
Example 94
Source File: AsyncHttpClientCatsHttpTest.scala    From sttp   with Apache License 2.0 5 votes vote down vote up
package sttp.client.asynchttpclient.cats

import java.util.concurrent.TimeoutException

import cats.effect.IO
import sttp.client._
import sttp.client.impl.cats.CatsTestBase
import sttp.client.testing.{CancelTest, HttpTest}

import scala.concurrent.duration._

class AsyncHttpClientCatsHttpTest extends HttpTest[IO] with CancelTest[IO, Nothing] with CatsTestBase {
  override implicit val backend: SttpBackend[IO, Nothing, NothingT] = AsyncHttpClientCatsBackend[IO]().unsafeRunSync()

  "illegal url exceptions" - {
    "should be wrapped in the effect wrapper" in {
      basicRequest.get(uri"ps://sth.com").send().toFuture().failed.map { e => e shouldBe a[IllegalArgumentException] }
    }
  }

  override def timeoutToNone[T](t: IO[T], timeoutMillis: Int): IO[Option[T]] =
    t.map(Some(_))
      .timeout(timeoutMillis.milliseconds)
      .handleErrorWith {
        case _: TimeoutException => IO(None)
        case e                   => throw e
      }

  override def throwsExceptionOnUnsupportedEncoding = false
} 
Example 95
Source File: StreamFs2.scala    From sttp   with Apache License 2.0 5 votes vote down vote up
package sttp.client.examples

object StreamFs2 extends App {
  import sttp.client._
  import sttp.client.asynchttpclient.fs2.AsyncHttpClientFs2Backend

  import cats.effect.{ContextShift, IO}
  import cats.instances.string._
  import fs2.{Stream, text}

  implicit val cs: ContextShift[IO] = IO.contextShift(scala.concurrent.ExecutionContext.global)

  def streamRequestBody(implicit backend: SttpBackend[IO, Stream[IO, Byte], NothingT]): IO[Unit] = {
    val stream: Stream[IO, Byte] = Stream.emits("Hello, world".getBytes)

    basicRequest
      .streamBody(stream)
      .post(uri"https://httpbin.org/post")
      .send()
      .map { response => println(s"RECEIVED:\n${response.body}") }
  }

  def streamResponseBody(implicit backend: SttpBackend[IO, Stream[IO, Byte], NothingT]): IO[Unit] = {
    basicRequest
      .body("I want a stream!")
      .post(uri"https://httpbin.org/post")
      .response(asStreamAlways[Stream[IO, Byte]])
      .send()
      .flatMap { response =>
        response.body.chunks
          .through(text.utf8DecodeC)
          .compile
          .foldMonoid
      }
      .map { body => println(s"RECEIVED:\n$body") }
  }

  val effect = AsyncHttpClientFs2Backend[IO]().flatMap { implicit backend =>
    streamRequestBody.flatMap(_ => streamResponseBody).guarantee(backend.close())
  }

  effect.unsafeRunSync()
} 
Example 96
Source File: HttpClientHighLevelFs2WebsocketTest.scala    From sttp   with Apache License 2.0 5 votes vote down vote up
package sttp.client.httpclient.fs2

import cats.effect.IO
import cats.implicits._
import sttp.client._
import sttp.client.httpclient.WebSocketHandler
import sttp.client.testing.websocket.HighLevelWebsocketTest
import sttp.client.ws.WebSocket
import sttp.client.testing.HttpTest.wsEndpoint

import scala.concurrent.duration._

class HttpClientHighLevelFs2WebsocketTest
    extends HighLevelWebsocketTest[IO, WebSocketHandler]
    with HttpClientFs2TestBase {

  override def createHandler: Option[Int] => IO[WebSocketHandler[WebSocket[IO]]] =
    Fs2WebSocketHandler[IO](_)

  it should "handle backpressure correctly" in {
    basicRequest
      .get(uri"$wsEndpoint/ws/echo")
      .openWebsocketF(createHandler(Some(3)))
      .flatMap { response =>
        val ws = response.result
        send(ws, 1000) >> eventually(10.millis, 500) { ws.isOpen.map(_ shouldBe true) }
      }
      .toFuture()
  }

  override def eventually[T](interval: FiniteDuration, attempts: Int)(f: => IO[T]): IO[T] = {
    def tryWithCounter(i: Int): IO[T] = {
      (IO.sleep(interval) >> f).recoverWith {
        case _: Exception if i < attempts => tryWithCounter(i + 1)
      }
    }
    tryWithCounter(0)
  }

} 
Example 97
Source File: Fs2AsyncQueue.scala    From sttp   with Apache License 2.0 5 votes vote down vote up
package sttp.client.impl.fs2

import cats.effect.{Effect, IO}
import fs2.concurrent.InspectableQueue
import sttp.client.ws.internal.AsyncQueue
import sttp.model.ws.WebSocketBufferFull

import scala.language.higherKinds

class Fs2AsyncQueue[F[_], A](queue: InspectableQueue[F, A])(implicit F: Effect[F]) extends AsyncQueue[F, A] {
  override def offer(t: A): Unit = {
    F.toIO(queue.offer1(t))
      .flatMap {
        case true  => IO.unit
        case false => IO.raiseError(new WebSocketBufferFull())
      }
      .unsafeRunSync()
  }

  override def poll: F[A] = queue.dequeue1
} 
Example 98
Source File: Launcher.scala    From slab   with Apache License 2.0 5 votes vote down vote up
// Example: A Slab server
//
// Guide for creating a Slab server
package com.criteo.slab.example

import java.net.URLDecoder

import cats.effect.IO
import com.criteo.slab.app.StateService.NotFoundError
import com.criteo.slab.app.WebServer
import com.criteo.slab.lib.InMemoryStore
import lol.http._
import org.slf4j.LoggerFactory

object Launcher {

  import SimpleBoard._

  import scala.concurrent.ExecutionContext.Implicits.global

  private val logger = LoggerFactory.getLogger(this.getClass)

  def main(args: Array[String]): Unit = {
    require(args.length == 1, "you must supply a port!")
    val port = args(0).toInt
    // You should provide codec for checked value types for values to be persistent in a store
    import InMemoryStore.codec
    // Define a value store for uploading and restoring history
    implicit val store = new InMemoryStore
    // Create a web server
    WebServer()
      // You can define custom routes, Slab web server is built with [lolhttp](https://github.com/criteo/lolhttp)
      .withRoutes(stateService => {
        case GET at "/api/heartbeat" => Ok("ok")
        case GET at url"/api/boards/$board/status" =>
          IO.fromFuture(IO(
            stateService
              .current(URLDecoder.decode(board, "UTF-8")).map(view => Ok(view.status.name))
              .recover {
                case NotFoundError(message) => NotFound(message)
                case e =>
                  logger.error(e.getMessage, e)
                  InternalServerError
              }
          ))
      })
      // Attach a board to the server
      .attach(board)
      // Launch the server at port
      .apply(port)
  }
} 
Example 99
Source File: Stryker4sMain.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.command

import pureconfig.error.ConfigReaderException
import stryker4s.command.config.ProcessRunnerConfig
import stryker4s.config.ConfigReader
import stryker4s.run.threshold.ErrorStatus
import pureconfig.generic.auto._
import cats.effect.IOApp
import cats.effect.{ExitCode, IO}

object Stryker4sMain extends IOApp {
  override def run(args: List[String]): IO[ExitCode] =
    IO {
      Stryker4sArgumentHandler.handleArgs(args)

      val processRunnerConfig: ProcessRunnerConfig =
        ConfigReader.readConfigOfType[ProcessRunnerConfig]() match {
          case Left(failures) => throw ConfigReaderException(failures)
          case Right(config)  => config
        }

      val result = new Stryker4sCommandRunner(processRunnerConfig).run()

      result match {
        case ErrorStatus => ExitCode.Error
        case _           => ExitCode.Success
      }
    }
} 
Example 100
Source File: Stryker4sMain.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.maven

import javax.inject.Inject
import org.apache.maven.plugin.{AbstractMojo, MojoFailureException}
import org.apache.maven.plugins.annotations.{Mojo, Parameter}
import org.apache.maven.project.MavenProject
import stryker4s.run.threshold.ErrorStatus
import scala.concurrent.ExecutionContext.Implicits.global
import cats.effect.{ContextShift, IO}
import scala.concurrent.ExecutionContext


@Mojo(name = "run")
class Stryker4sMain @Inject() (@Parameter(defaultValue = "${project}") project: MavenProject) extends AbstractMojo {
  override def execute(): Unit = {
    implicit val cs: ContextShift[IO] = IO.contextShift(implicitly[ExecutionContext])
    new Stryker4sMavenRunner(project).run() match {
      case ErrorStatus => throw new MojoFailureException("Mutation score was below configured threshold")
      case _           =>
    }
  }
} 
Example 101
Source File: JsonReporter.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.report

import grizzled.slf4j.Logging
import stryker4s.config.Config
import stryker4s.files.FileIO
import mutationtesting.MutationTestReport
import cats.effect.IO
import java.nio.file.Path

class JsonReporter(fileIO: FileIO)(implicit config: Config) extends FinishedRunReporter with Logging {

  def writeReportJsonTo(file: Path, report: MutationTestReport): IO[Unit] = {
    import io.circe.syntax._
    import mutationtesting.MutationReportEncoder._
    val json = report.asJson.noSpaces
    fileIO.createAndWrite(file, json)
  }

  override def reportRunFinished(runReport: FinishedRunReport): IO[Unit] = {
    val targetLocation = config.baseDir / s"target/stryker4s-report-${runReport.timestamp}/"
    val resultLocation = targetLocation / "report.json"

    writeReportJsonTo(resultLocation.path, runReport.report) *>
      IO(info(s"Written JSON report to $resultLocation"))
  }
} 
Example 102
Source File: HtmlReporter.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.report

import grizzled.slf4j.Logging
import mutationtesting._
import stryker4s.config.Config
import stryker4s.files.FileIO
import cats.effect.IO
import cats.Parallel
import java.nio.file.Path

class HtmlReporter(fileIO: FileIO)(implicit config: Config, p: Parallel[IO]) extends FinishedRunReporter with Logging {

  private val title = "Stryker4s report"
  private val mutationTestElementsName = "mutation-test-elements.js"
  private val htmlReportResource = s"/mutation-testing-elements/$mutationTestElementsName"
  private val reportFilename = "report.js"

  private val indexHtml: String =
    s"""<!DOCTYPE html>
       |<html lang="en">
       |<head>
       |  <meta charset="UTF-8">
       |  <meta name="viewport" content="width=device-width, initial-scale=1.0">
       |  <script src="mutation-test-elements.js"></script>
       |</head>
       |<body>
       |  <mutation-test-report-app title-postfix="$title">
       |    Your browser doesn't support <a href="https://caniuse.com/#search=custom%20elements">custom elements</a>.
       |    Please use a latest version of an evergreen browser (Firefox, Chrome, Safari, Opera, etc).
       |  </mutation-test-report-app>
       |  <script src="$reportFilename"></script>
       |</body>
       |</html>""".stripMargin

  def writeMutationTestElementsJsTo(file: Path): IO[Unit] =
    fileIO.createAndWriteFromResource(file, htmlReportResource)

  def writeIndexHtmlTo(file: Path): IO[Unit] =
    fileIO.createAndWrite(file, indexHtml)

  def writeReportJsTo(file: Path, report: MutationTestReport): IO[Unit] = {
    import io.circe.syntax._
    import mutationtesting.MutationReportEncoder._
    val json = report.asJson.noSpaces
    val reportContent = s"document.querySelector('mutation-test-report-app').report = $json"
    fileIO.createAndWrite(file, reportContent)
  }

  override def reportRunFinished(runReport: FinishedRunReport): IO[Unit] = {
    val targetLocation = config.baseDir / s"target/stryker4s-report-${runReport.timestamp}/"

    val mutationTestElementsLocation = targetLocation / mutationTestElementsName
    val indexLocation = targetLocation / "index.html"
    val reportLocation = targetLocation / reportFilename

    val reportsWriting = writeIndexHtmlTo(indexLocation.path) &>
      writeReportJsTo(reportLocation.path, runReport.report) &>
      writeMutationTestElementsJsTo(mutationTestElementsLocation.path)

    reportsWriting *>
      IO(info(s"Written HTML report to $indexLocation"))
  }
} 
Example 103
Source File: MutationRunReporter.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.report
import mutationtesting._
import stryker4s.model.{Mutant, MutantRunResult}
import cats.effect.IO

sealed trait MutationRunReporter

trait ProgressReporter extends MutationRunReporter {
  def reportMutationStart(mutant: Mutant): IO[Unit]

  def reportMutationComplete(result: MutantRunResult, totalMutants: Int): IO[Unit]
}

trait FinishedRunReporter extends MutationRunReporter {
  def reportRunFinished(runReport: FinishedRunReport): IO[Unit]
}

final case class FinishedRunReport(report: MutationTestReport, metrics: MetricsResult) {
  @transient val timestamp: Long = System.currentTimeMillis()
} 
Example 104
Source File: DashboardReporter.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.report

import grizzled.slf4j.Logging
import mutationtesting.{MetricsResult, MutationTestReport}
import stryker4s.report.dashboard.DashboardConfigProvider
import stryker4s.report.model._
import stryker4s.config.Full
import stryker4s.config.MutationScoreOnly
import sttp.client._
import sttp.client.circe._
import sttp.model.MediaType
import sttp.model.StatusCode
import cats.effect.IO

class DashboardReporter(dashboardConfigProvider: DashboardConfigProvider)(implicit
    httpBackend: SttpBackend[IO, Nothing, NothingT]
) extends FinishedRunReporter
    with Logging {

  override def reportRunFinished(runReport: FinishedRunReport): IO[Unit] =
    dashboardConfigProvider.resolveConfig() match {
      case Left(configKey) =>
        IO {
          warn(s"Could not resolve dashboard configuration key '$configKey', not sending report")
        }
      case Right(dashboardConfig) =>
        val request = buildRequest(dashboardConfig, runReport.report, runReport.metrics)
        request
          .send()
          .map(response => logResponse(response))
    }

  def buildRequest(dashConfig: DashboardConfig, report: MutationTestReport, metrics: MetricsResult) = {
    import io.circe.{Decoder, Encoder}
    implicit val decoder: Decoder[DashboardPutResult] = Decoder.forProduct1("href")(DashboardPutResult.apply)
    // Separate so any slashes won't be escaped in project or version
    val baseUrl = s"${dashConfig.baseUrl}/api/reports/${dashConfig.project}/${dashConfig.version}"
    val uri = uri"$baseUrl?module=${dashConfig.module}"
    val request = basicRequest
      .header("X-Api-Key", dashConfig.apiKey)
      .contentType(MediaType.ApplicationJson)
      .response(asJson[DashboardPutResult])
      .put(uri)
    dashConfig.reportType match {
      case Full =>
        import mutationtesting.MutationReportEncoder._
        request
          .body(report)
      case MutationScoreOnly =>
        implicit val encoder: Encoder[ScoreOnlyReport] = Encoder.forProduct1("mutationScore")(r => r.mutationScore)
        request
          .body(ScoreOnlyReport(metrics.mutationScore))
    }
  }

  def logResponse(response: Response[Either[ResponseError[io.circe.Error], DashboardPutResult]]): Unit =
    response.body match {
      case Left(HttpError(errorBody)) =>
        response.code match {
          case StatusCode.Unauthorized =>
            error(
              s"Error HTTP PUT '$errorBody'. Status code 401 Unauthorized. Did you provide the correct api key in the 'STRYKER_DASHBOARD_API_KEY' environment variable?"
            )
          case statusCode =>
            error(
              s"Failed to PUT report to dashboard. Response status code: ${statusCode.code}. Response body: '${errorBody}'"
            )
        }
      case Left(DeserializationError(original, error)) =>
        warn(s"Dashboard report was sent successfully, but could not decode the response: '$original'. Error:", error)
      case Right(DashboardPutResult(href)) =>
        info(s"Sent report to dashboard. Available at $href")
    }
} 
Example 105
Source File: Reporter.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.report

import grizzled.slf4j.Logging
import stryker4s.config._
import stryker4s.files.DiskFileIO
import stryker4s.model.{Mutant, MutantRunResult}
import stryker4s.report.dashboard.DashboardConfigProvider
import cats.implicits._
import cats.effect.IO
import sttp.client.asynchttpclient.cats.AsyncHttpClientCatsBackend
import cats.effect.ContextShift

class Reporter(implicit config: Config, cs: ContextShift[IO])
    extends FinishedRunReporter
    with ProgressReporter
    with Logging {

  lazy val reporters: Iterable[MutationRunReporter] = config.reporters map {
    case Console => new ConsoleReporter()
    case Html    => new HtmlReporter(new DiskFileIO())
    case Json    => new JsonReporter(new DiskFileIO())
    case Dashboard =>
      AsyncHttpClientCatsBackend[IO]()
        .map { implicit backend =>
          new DashboardReporter(new DashboardConfigProvider(sys.env))
        }
        // TODO: Figure out some other way to do this?
        .unsafeRunSync()

  }

  private[this] lazy val progressReporters = reporters collect { case r: ProgressReporter => r }
  private[this] lazy val finishedRunReporters = reporters collect { case r: FinishedRunReporter => r }

  override def reportMutationStart(mutant: Mutant): IO[Unit] =
    reportAll[ProgressReporter](
      progressReporters,
      _.reportMutationStart(mutant)
    )

  override def reportMutationComplete(result: MutantRunResult, totalMutants: Int): IO[Unit] =
    reportAll[ProgressReporter](
      progressReporters,
      _.reportMutationComplete(result, totalMutants)
    )

  override def reportRunFinished(runReport: FinishedRunReport): IO[Unit] = {
    reportAll[FinishedRunReporter](
      finishedRunReporters,
      reporter => reporter.reportRunFinished(runReport)
    )
  }

  
  private def reportAll[T](reporters: Iterable[T], reportF: T => IO[Unit]): IO[Unit] = {
    reporters.toList
      .parTraverse { reporter =>
        reportF(reporter).attempt
      }
      .map { _ collect { case Left(f) => f } }
      .flatMap { failed =>
        if (failed.nonEmpty) IO {
          warn(s"${failed.size} reporter(s) failed to report:")
          failed.foreach(warn(_))
        }
        else IO.unit
      }
  }
} 
Example 106
Source File: fileIO.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.files
import cats.effect.IO
import fs2._
import fs2.io.readInputStream
import fs2.io.file._
import cats.effect.Blocker
import cats.effect.ContextShift
import cats.effect.Sync
import java.nio.file.Path
sealed trait FileIO {
  def createAndWriteFromResource(file: Path, resource: String): IO[Unit]

  def createAndWrite(file: Path, content: String): IO[Unit]
}

class DiskFileIO()(implicit cs: ContextShift[IO], s: Sync[IO]) extends FileIO {
  override def createAndWriteFromResource(file: Path, resourceName: String): IO[Unit] =
    Blocker[IO].use { blocker =>
      val stream = IO { getClass().getResourceAsStream(resourceName) }

      createDirectories(blocker, file.getParent()) *>
        readInputStream(stream, 8192, blocker)
          .through(writeAll(file, blocker))
          .compile
          .drain
    }

  override def createAndWrite(file: Path, content: String): IO[Unit] =
    Blocker[IO].use { blocker =>
      createDirectories(blocker, file.getParent()) *>
        Stream(content)
          .through(text.utf8Encode)
          .through(writeAll(file, blocker))
          .compile
          .drain
    }
} 
Example 107
Source File: Stryker4sRunner.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.run

import stryker4s.Stryker4s
import stryker4s.config.{Config, ConfigReader}
import stryker4s.mutants.Mutator
import stryker4s.mutants.applymutants.ActiveMutationContext.ActiveMutationContext
import stryker4s.mutants.applymutants.{MatchBuilder, StatementTransformer}
import stryker4s.mutants.findmutants.{FileCollector, MutantFinder, MutantMatcher, SourceCollector}
import stryker4s.report.Reporter
import stryker4s.run.process.ProcessRunner
import stryker4s.run.threshold.ScoreStatus
import cats.effect.ContextShift
import cats.effect.IO

trait Stryker4sRunner {
  def run()(implicit cs: ContextShift[IO]): ScoreStatus = {
    implicit val config: Config = ConfigReader.readConfig()

    val collector = new FileCollector(ProcessRunner())
    val stryker4s = new Stryker4s(
      collector,
      new Mutator(new MutantFinder(new MutantMatcher), new StatementTransformer, new MatchBuilder(mutationActivation)),
      resolveRunner(collector, new Reporter())
    )
    stryker4s.run()
  }

  def resolveRunner(collector: SourceCollector, reporter: Reporter)(implicit config: Config): MutantRunner

  def mutationActivation: ActiveMutationContext
} 
Example 108
Source File: ProcessRunner.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.run.process

import better.files.File
import grizzled.slf4j.Logging

import scala.concurrent.duration.{Duration, MINUTES}
import scala.sys.process.{Process, ProcessLogger}
import scala.util.Try
import cats.effect.IO

trait ProcessRunner extends Logging {
  def apply(command: Command, workingDir: File): Try[Seq[String]] = {
    Try {
      Process(s"${command.command} ${command.args}", workingDir.toJava)
        .!!<(ProcessLogger(debug(_)))
        .linesIterator
        .toSeq
    }
  }

  def apply(command: Command, workingDir: File, envVar: (String, String)): Try[Int] = {
    val mutantProcess = Process(s"${command.command} ${command.args}", workingDir.toJava, envVar)
      .run(ProcessLogger(debug(_)))

    val exitCodeFuture = IO(mutantProcess.exitValue())
    // TODO: Maybe don't use unsafeRunTimed
    // TODO: Use timeout decided by initial test-run duration
    Try(exitCodeFuture.unsafeRunTimed(Duration(2, MINUTES)).get)
  }
}

object ProcessRunner {
  private def isWindows: Boolean = sys.props("os.name").toLowerCase.contains("windows")

  def apply(): ProcessRunner = {
    if (isWindows) new WindowsProcessRunner
    else new UnixProcessRunner
  }
} 
Example 109
Source File: JsonReporterTest.scala    From stryker4s   with Apache License 2.0 5 votes vote down vote up
package stryker4s.report

import mutationtesting.{Metrics, MutationTestReport, Thresholds}
import org.mockito.captor.ArgCaptor
import stryker4s.config.Config
import stryker4s.files.FileIO
import stryker4s.scalatest.LogMatchers
import stryker4s.testutil.{MockitoSuite, Stryker4sSuite}
import java.nio.file.Path
import cats.effect.IO

class JsonReporterTest extends Stryker4sSuite with MockitoSuite with LogMatchers {
  describe("reportJson") {
    it("should contain the report") {
      implicit val config: Config = Config.default
      val mockFileIO = mock[FileIO]
      when(mockFileIO.createAndWrite(any[Path], any[String])).thenReturn(IO.unit)
      val sut = new JsonReporter(mockFileIO)
      val testFile = (config.baseDir / "foo.bar").path
      val report = MutationTestReport(thresholds = Thresholds(100, 0), files = Map.empty)

      sut
        .writeReportJsonTo(testFile, report)
        .unsafeRunSync()
      verify(mockFileIO).createAndWrite(eqTo(testFile), any[String])
    }
  }

  describe("reportRunFinished") {
    implicit val config: Config = Config.default
    val stryker4sReportFolderRegex = ".*target(/|\\\\)stryker4s-report-(\\d*)(/|\\\\)[a-z-]*\\.[a-z]*$"

    it("should write the report file to the report directory") {
      val mockFileIO = mock[FileIO]
      when(mockFileIO.createAndWrite(any[Path], any[String])).thenReturn(IO.unit)
      val sut = new JsonReporter(mockFileIO)
      val report = MutationTestReport(thresholds = Thresholds(100, 0), files = Map.empty)
      val metrics = Metrics.calculateMetrics(report)

      sut
        .reportRunFinished(FinishedRunReport(report, metrics))
        .unsafeRunSync()

      val writtenFilesCaptor = ArgCaptor[Path]
      verify(mockFileIO, times(1)).createAndWrite(writtenFilesCaptor, any[String])
      val paths = writtenFilesCaptor.values.map(_.toString())
      all(paths) should fullyMatch regex stryker4sReportFolderRegex
      writtenFilesCaptor.values.map(_.getFileName().toString()) should contain only "report.json"
    }

    it("should info log a message") {
      val mockFileIO = mock[FileIO]
      when(mockFileIO.createAndWrite(any[Path], any[String])).thenReturn(IO.unit)
      val sut = new JsonReporter(mockFileIO)
      val report = MutationTestReport(thresholds = Thresholds(100, 0), files = Map.empty)
      val metrics = Metrics.calculateMetrics(report)

      val captor = ArgCaptor[Path]
      sut
        .reportRunFinished(FinishedRunReport(report, metrics))
        .unsafeRunSync()

      verify(mockFileIO).createAndWrite(captor.capture, any[String])
      s"Written JSON report to ${captor.value}" shouldBe loggedAsInfo
    }
  }
} 
Example 110
Source File: EffectInstancesLawsSuite.scala    From meow-mtl   with MIT License 5 votes vote down vote up
package com.olegpy.meow.effects

import cats.effect.IO
import cats.effect.concurrent.Ref
import cats.effect.laws.util.TestContext
import cats.implicits._
import cats.effect.laws.discipline.arbitrary._
import cats.effect.laws.util.TestInstances._
import cats.mtl.laws.discipline._
import minitest.SimpleTestSuite
import minitest.laws.Checkers
import org.typelevel.discipline.Laws
import scala.concurrent.duration._

object EffectInstancesLawsSuite extends SimpleTestSuite with Checkers {
  private def checkAll(name: String)(ruleSet: TestContext => Laws#RuleSet) = {
    implicit val ctx = TestContext()

    for ((id, prop) <- ruleSet(ctx).all.properties)
      test(name + "." + id) {
        ctx.tick(1.day)
        check(prop)
      }
  }

  checkAll("Ref.runAsk") { implicit ctx =>
    Ref.unsafe[IO, Int](0).runAsk(ev =>
      ApplicativeAskTests(ev).applicativeAsk[Int]
    )
  }

  checkAll("Ref.runState") { implicit ctx =>
    Ref.unsafe[IO, Int](0).runState(ev =>
      MonadStateTests(ev).monadState[Int]
    )
  }

  checkAll("Ref.runTell") { implicit ctx =>
    Ref.unsafe[IO, Int](0).runTell(ev =>
      FunctorTellTests(ev).functorTell[Int]
    )
  }

  checkAll("Consumer.runTell") { implicit ctx =>
    case object DummyErr extends Throwable
    def fun(x: Int) =
      if (x == 1) IO.raiseError[Unit](DummyErr)
      else IO.unit
    Consumer(fun _).runTell(ev =>
      FunctorTellTests(ev).functorTell[Int]
    )

  }
} 
Example 111
Source File: FS2CronTest.scala    From fs2-cron   with Apache License 2.0 5 votes vote down vote up
package eu.timepit.fs2cron

import cats.effect.{ContextShift, IO, Timer}
import cron4s.Cron
import cron4s.expr.CronExpr
import scala.concurrent.ExecutionContext
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

class FS2CronTest extends AnyFunSuite with Matchers {
  implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)
  val evenSeconds: CronExpr = Cron.unsafeParse("*/2 * * ? * *")
  def isEven(i: Int): Boolean = i % 2 == 0

  test("awakeEveryCron") {
    val s1 = awakeEveryCron[IO](evenSeconds) >> evalNow[IO]
    val s2 = s1.map(_.getSecond).take(2).forall(isEven)
    s2.compile.last.map(_.getOrElse(false)).unsafeRunSync()
  }

  test("sleepCron") {
    val s1 = sleepCron[IO](evenSeconds) >> evalNow[IO]
    val s2 = s1.map(_.getSecond).forall(isEven)
    s2.compile.last.map(_.getOrElse(false)).unsafeRunSync()
  }

  test("schedule") {
    implicit val ctxShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
    val everySecond: CronExpr = Cron.unsafeParse("* * * ? * *")
    val s1 = schedule(List(everySecond -> evalNow[IO], evenSeconds -> evalNow[IO])).map(_.getSecond)

    val seconds = s1.take(3).compile.toList.unsafeRunSync()
    seconds.count(isEven) shouldBe 2
    seconds.count(!isEven(_)) shouldBe 1
  }
} 
Example 112
Source File: DownloadableFile.scala    From polynote   with Apache License 2.0 5 votes vote down vote up
package polynote.kernel.util

import java.io.{File, FileInputStream, InputStream}
import java.net.{HttpURLConnection, URI}
import java.util.ServiceLoader

import scala.collection.JavaConverters._
import cats.effect.IO
import zio.{RIO, ZIO}
import zio.blocking.{Blocking, effectBlocking}

trait DownloadableFile {
  def openStream: IO[InputStream]
  def size: IO[Long]
}

trait DownloadableFileProvider {
  def getFile(uri: URI): Option[DownloadableFile] = provide.lift(uri)

  def provide: PartialFunction[URI, DownloadableFile]

  def protocols: Seq[String]

  object Supported {
    def unapply(arg: URI): Option[URI] = {
      Option(arg.getScheme).flatMap(scheme => protocols.find(_ == scheme)).map(_ => arg)
    }
  }
}

object DownloadableFileProvider {
  private lazy val unsafeLoad = ServiceLoader.load(classOf[DownloadableFileProvider]).iterator.asScala.toList

  def isSupported(uri: URI): RIO[Blocking, Boolean] = effectBlocking(unsafeLoad).map { providers =>
    Option(uri.getScheme).exists(providers.flatMap(_.protocols).contains)
  }

  def getFile(uri: URI): ZIO[Blocking, Throwable, DownloadableFile] = {
    effectBlocking(unsafeLoad).map {
      providers =>
        for {
          scheme <- Option(uri.getScheme)
          provider <- providers.find(_.protocols.contains(scheme))
          file <- provider.getFile(uri)
        } yield file
    }.someOrFail(new Exception(s"Unable to find provider for uri $uri"))
  }
}

class HttpFileProvider extends DownloadableFileProvider {
  override def protocols: Seq[String] = Seq("http", "https")

  override def provide: PartialFunction[URI, DownloadableFile] = {
    case Supported(uri) => HTTPFile(uri)
  }
}

case class HTTPFile(uri: URI) extends DownloadableFile {
  override def openStream: IO[InputStream] = IO(uri.toURL.openStream())

  override def size: IO[Long] = IO(uri.toURL.openConnection().asInstanceOf[HttpURLConnection]).bracket { conn =>
    IO {
      conn.setRequestMethod("HEAD")
      conn.getContentLengthLong
    }
  } { conn => IO(conn.disconnect())}
}

class LocalFileProvider extends DownloadableFileProvider {
  override def protocols: Seq[String] = Seq("file")

  override def provide: PartialFunction[URI, DownloadableFile] = {
    case Supported(uri) => LocalFile(uri)
  }
}

case class LocalFile(uri: URI) extends DownloadableFile {
  lazy val file = new File(uri)
  override def openStream: IO[InputStream] = IO(new FileInputStream(file))

  override def size: IO[Long] = IO.pure(file.length())
} 
Example 113
Source File: CatsImplicitsSpec.scala    From neotypes   with MIT License 5 votes vote down vote up
package neotypes.cats.effect

import cats.{Applicative, Monad}
import cats.effect.{Async, IO, Resource}
import cats.effect.implicits._
import cats.implicits._
import neotypes.{BaseIntegrationSpec, Driver, Session}
import neotypes.cats.effect.implicits._
import neotypes.implicits.all._
import org.neo4j.driver.v1.exceptions.ClientException


final class CatsImplicitsSpec extends BaseIntegrationSpec[IO](IOTestkit) {
  it should "work with cats implicits and neotypes implicits" in {
    def test1[F[_]: Applicative]: F[Unit] = Applicative[F].unit
    def test2[F[_]: Monad]: F[Unit] = ().pure[F]

    def makeSession[F[_]: Async]: Resource[F, Session[F]] =
      Resource
        .make(Async[F].delay(new Driver[F](this.driver)))(_.close)
        .flatMap(_.session)

    def useSession[F[_]: Async]: F[String] = makeSession[F].use { s =>
      (test1[F] *> test2[F]).flatMap { _=>
        """match (p:Person {name: "Charlize Theron"}) return p.name"""
          .query[String]
          .single(s)
      }
    }

    useSession[IO].unsafeToFuture().map {
      name => assert(name == "Charlize Theron")
    }
  }

  override val initQuery: String = BaseIntegrationSpec.DEFAULT_INIT_QUERY
} 
Example 114
Source File: DeepBindBenchmark.scala    From cats-effect   with Apache License 2.0 5 votes vote down vote up
package cats.effect.benchmarks

import java.util.concurrent.TimeUnit
import cats.effect.{ContextShift, IO}
import org.openjdk.jmh.annotations._
import scala.concurrent.ExecutionContext.Implicits


@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class DeepBindBenchmark {
  implicit val cs: ContextShift[IO] = IO.contextShift(Implicits.global)

  @Param(Array("3000"))
  var size: Int = _

  @Benchmark
  def pure(): Int = {
    def loop(i: Int): IO[Int] =
      for {
        j <- IO.pure(i)
        _ <- if (j > size) IO.pure(j) else loop(j + 1)
      } yield j

    loop(0).unsafeRunSync()
  }

  @Benchmark
  def delay(): Int = {
    def loop(i: Int): IO[Int] =
      for {
        j <- IO(i)
        _ <- if (j > size) IO(j) else loop(j + 1)
      } yield j

    loop(0).unsafeRunSync()
  }

  @Benchmark
  def async(): Int = {
    def loop(i: Int): IO[Int] =
      for {
        j <- IO(i)
        _ <- IO.shift
        _ <- if (j > size) IO(j) else loop(j + 1)
      } yield j

    loop(0).unsafeRunSync()
  }
} 
Example 115
Source File: AttemptBenchmark.scala    From cats-effect   with Apache License 2.0 5 votes vote down vote up
package cats.effect.benchmarks

import java.util.concurrent.TimeUnit

import cats.effect.IO
import org.openjdk.jmh.annotations._


@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class AttemptBenchmark {
  @Param(Array("10000"))
  var size: Int = _

  @Benchmark
  def happyPath(): Int = {
    def loop(i: Int): IO[Int] =
      if (i < size) IO.pure(i + 1).attempt.flatMap(_.fold(IO.raiseError, loop))
      else IO.pure(i)

    loop(0).unsafeRunSync()
  }

  @Benchmark
  def errorRaised(): Int = {
    val dummy = new RuntimeException("dummy")
    val id = IO.pure[Int] _

    def loop(i: Int): IO[Int] =
      if (i < size)
        IO.raiseError[Int](dummy)
          .flatMap(x => IO.pure(x + 1))
          .attempt
          .flatMap(_.fold(_ => loop(i + 1), id))
      else
        IO.pure(i)

    loop(0).unsafeRunSync()
  }
} 
Example 116
Source File: MapStreamBenchmark.scala    From cats-effect   with Apache License 2.0 5 votes vote down vote up
package cats.effect.benchmarks

import java.util.concurrent.TimeUnit
import cats.effect.IO
import org.openjdk.jmh.annotations._


@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class MapStreamBenchmark {
  import MapStreamBenchmark.streamTest

  @Benchmark
  def one(): Long = streamTest(12000, 1)

  @Benchmark
  def batch30(): Long = streamTest(1000, 30)

  @Benchmark
  def batch120(): Long = streamTest(100, 120)
}

object MapStreamBenchmark {
  def streamTest(times: Int, batchSize: Int): Long = {
    var stream = range(0, times)
    var i = 0
    while (i < batchSize) {
      stream = mapStream(addOne)(stream)
      i += 1
    }
    sum(0)(stream).unsafeRunSync()
  }

  final case class Stream(value: Int, next: IO[Option[Stream]])
  val addOne = (x: Int) => x + 1

  def range(from: Int, until: Int): Option[Stream] =
    if (from < until)
      Some(Stream(from, IO(range(from + 1, until))))
    else
      None

  def mapStream(f: Int => Int)(box: Option[Stream]): Option[Stream] =
    box match {
      case Some(Stream(value, next)) =>
        Some(Stream(f(value), next.map(mapStream(f))))
      case None =>
        None
    }

  def sum(acc: Long)(box: Option[Stream]): IO[Long] =
    box match {
      case Some(Stream(value, next)) =>
        next.flatMap(sum(acc + value))
      case None =>
        IO.pure(acc)
    }
} 
Example 117
Source File: MapCallsBenchmark.scala    From cats-effect   with Apache License 2.0 5 votes vote down vote up
package cats.effect.benchmarks

import java.util.concurrent.TimeUnit
import cats.effect.IO
import org.openjdk.jmh.annotations._


@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class MapCallsBenchmark {
  import MapCallsBenchmark.test

  @Benchmark
  def one(): Long = test(12000, 1)

  @Benchmark
  def batch30(): Long = test(12000 / 30, 30)

  @Benchmark
  def batch120(): Long = test(12000 / 120, 120)
}

object MapCallsBenchmark {
  def test(iterations: Int, batch: Int): Long = {
    val f = (x: Int) => x + 1
    var io = IO(0)

    var j = 0
    while (j < batch) { io = io.map(f); j += 1 }

    var sum = 0L
    var i = 0
    while (i < iterations) {
      sum += io.unsafeRunSync()
      i += 1
    }
    sum
  }
} 
Example 118
Source File: CpgServerMain.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.cpgserver

import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._
import org.http4s.implicits._
import org.http4s.server.blaze.BlazeServerBuilder

import io.shiftleft.cpgserver.config.ServerConfiguration
import io.shiftleft.cpgserver.cpg.DummyCpgProvider
import io.shiftleft.cpgserver.query.{DefaultAmmoniteExecutor, ServerAmmoniteExecutor}
import io.shiftleft.cpgserver.route.{CpgRoute, HttpErrorHandler, SwaggerRoute}

object CpgServerMain extends IOApp {

  private val banner: String =
    """| ██████╗██████╗  ██████╗     ███████╗███████╗██████╗ ██╗   ██╗███████╗██████╗
       |██╔════╝██╔══██╗██╔════╝     ██╔════╝██╔════╝██╔══██╗██║   ██║██╔════╝██╔══██╗
       |██║     ██████╔╝██║  ███╗    ███████╗█████╗  ██████╔╝██║   ██║█████╗  ██████╔╝
       |██║     ██╔═══╝ ██║   ██║    ╚════██║██╔══╝  ██╔══██╗╚██╗ ██╔╝██╔══╝  ██╔══██╗
       |╚██████╗██║     ╚██████╔╝    ███████║███████╗██║  ██║ ╚████╔╝ ███████╗██║  ██║
       | ╚═════╝╚═╝      ╚═════╝     ╚══════╝╚══════╝╚═╝  ╚═╝  ╚═══╝  ╚══════╝╚═╝  ╚═╝
       |""".stripMargin

  private val cpgProvider: DummyCpgProvider =
    new DummyCpgProvider

  private val ammoniteExecutor: ServerAmmoniteExecutor =
    new DefaultAmmoniteExecutor

  private implicit val httpErrorHandler: HttpErrorHandler =
    CpgRoute.CpgHttpErrorHandler

  private val serverConfig: ServerConfiguration =
    ServerConfiguration.config.getOrElse(ServerConfiguration.default)

  private val httpRoutes =
    CpgRoute(cpgProvider, ammoniteExecutor, serverConfig.files).routes <+> SwaggerRoute().routes

  override def run(args: List[String]): IO[ExitCode] = {
    BlazeServerBuilder[IO]
      .withBanner(List(banner))
      .bindHttp(serverConfig.port, serverConfig.host)
      .withHttpApp(httpRoutes.orNotFound)
      .serve
      .compile
      .drain
      .as(ExitCode.Success)
  }
} 
Example 119
Source File: SwaggerRoute.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.cpgserver.route

import java.util.concurrent.Executors

import scala.concurrent.ExecutionContext
import cats.data.OptionT
import cats.effect.{Blocker, ContextShift, IO}
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.io._
import org.http4s.headers.Location
import org.webjars.WebJarAssetLocator
import io.shiftleft.cpgserver.route.CpgRoute.ApiError

final class SwaggerRoute {

  private val blockingEc = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor)
  private val blocker = Blocker.liftExecutionContext(blockingEc)
  private implicit val blockingCs: ContextShift[IO] = IO.contextShift(blockingEc)

  private val swaggerUiVersion = IO { new WebJarAssetLocator().getWebJars.get("swagger-ui") }
  private val swaggerUiResources = swaggerUiVersion.map { ver =>
    s"/META-INF/resources/webjars/swagger-ui/$ver"
  }
  private val swaggerUiPath = Path("swagger-ui")

  val routes: HttpRoutes[IO] = HttpRoutes.of {
    case GET -> Root / ("swagger-ui" | "docs") =>
      PermanentRedirect(Location(Uri.unsafeFromString("swagger-ui/index.html")))

    // TODO discuss with jacob: according to scalac this is unreachable... commenting for now since it probably never worked anyway
    case req @ GET -> (Root | `swaggerUiPath`) / "swagger.yaml" =>
      StaticFile
        .fromResource("/swagger.yaml", blocker, Some(req))
        .getOrElseF(InternalServerError(ApiError("Swagger documentation is missing.").asJson))

    case req @ GET -> path if path.startsWith(swaggerUiPath) => {
      val file = path.toList.tail.mkString("/", "/", "") match {
        case f if f == "/index.html" =>
          StaticFile.fromResource[IO]("/swagger-ui/index.html", blocker, Some(req))
        case f =>
          OptionT.liftF(swaggerUiResources).flatMap { resources =>
            StaticFile.fromResource[IO](resources + f, blocker, Some(req))
          }
      }
      file.getOrElseF(InternalServerError(ApiError(s"Requested file [$file] is missing.").asJson))
    }
  }
}

object SwaggerRoute {
  def apply(): SwaggerRoute =
    new SwaggerRoute
} 
Example 120
Source File: HttpErrorHandler.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.cpgserver.route

import cats.data.{Kleisli, OptionT}
import cats.effect.IO
import org.http4s.{HttpRoutes, Request, Response}

trait HttpErrorHandler {
  def handle(routes: HttpRoutes[IO]): HttpRoutes[IO]
}

object HttpErrorHandler {

  def apply(routes: HttpRoutes[IO])(handler: PartialFunction[Throwable, IO[Response[IO]]]): HttpRoutes[IO] = {
    Kleisli { req: Request[IO] =>
      OptionT {
        routes.run(req).value.handleErrorWith { e =>
          if (handler.isDefinedAt(e)) handler(e).map(Option(_))
          else IO.raiseError(e)
        }
      }
    }
  }
} 
Example 121
Source File: DummyCpgProviderSpec.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.cpgserver.cpg

import java.util.UUID

import scala.concurrent.ExecutionContext
import cats.data.OptionT
import cats.effect.{ContextShift, IO}
import org.scalatest.concurrent.Eventually

import io.shiftleft.codepropertygraph.Cpg
import io.shiftleft.cpgserver.BaseSpec
import io.shiftleft.cpgserver.query.CpgOperationResult

import scala.concurrent.duration._
import scala.language.postfixOps

class DummyCpgProviderSpec extends BaseSpec with Eventually {

  private implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)

  private def withNewCpgProvider[T](f: DummyCpgProvider => T): T = {
    f(new DummyCpgProvider)
  }

  "Creating a CPG" should {
    "return a UUID referencing the eventual CPG" in withNewCpgProvider { cpgProvider =>
      noException should be thrownBy cpgProvider.createCpg(Set.empty).unsafeRunSync()
    }
  }

  "Retrieving a CPG" should {
    "return a success if the CPG was created successfully" in withNewCpgProvider { cpgProvider =>
      val cpgId = cpgProvider.createCpg(Set.empty).unsafeRunSync()

      eventually(timeout(10 seconds), interval(1 seconds)) {
        cpgProvider.retrieveCpg(cpgId).value.unsafeRunSync() shouldBe defined
      }
    }

    "return an empty OptionT if the CPG does not exist" in withNewCpgProvider { cpgProvider =>
      cpgProvider.retrieveCpg(UUID.randomUUID) shouldBe OptionT.none[IO, CpgOperationResult[Cpg]]
    }
  }
} 
Example 122
Source File: ServerAmmoniteExecutorSpec.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.cpgserver.query

import cats.effect.{ContextShift, IO}
import org.scalatest.{Matchers, WordSpec}

import io.shiftleft.codepropertygraph.Cpg

class ServerAmmoniteExecutorSpec extends WordSpec with Matchers {

  private implicit val cs: ContextShift[IO] = IO.contextShift(scala.concurrent.ExecutionContext.global)

  private class DummyServerAmmoniteExecutor extends ServerAmmoniteExecutor {
    override protected def predef: String = "import io.shiftleft.semanticcpg.language._"
  }

  private def withServerExecutor[T](f: ServerAmmoniteExecutor => T): T = {
    f(new DummyServerAmmoniteExecutor)
  }

  "A ServerAmmoniteExecutor" should {
    "run a query synchronously" in withServerExecutor { executor =>
      executor.executeQuerySync(Cpg.emptyCpg, "cpg.method.l").unsafeRunSync() should matchPattern {
        case CpgOperationSuccess("List()") =>
      }
    }
  }
} 
Example 123
Source File: Http4sSpec.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.cpgserver.route

import cats.effect.IO
import org.http4s.{EntityDecoder, Response, Status}

import io.shiftleft.cpgserver.BaseSpec

trait Http4sSpec extends BaseSpec {

  // Helpfully lifted from https://http4s.org/v0.20/testing/
  def check[A](actual: IO[Response[IO]], expectedStatus: Status, expectedBody: Option[A] = None)(
      implicit ev: EntityDecoder[IO, A]
  ): Boolean = {
    val actualResp = actual.unsafeRunSync
    val statusCheck = actualResp.status == expectedStatus
    val bodyCheck =
      expectedBody.fold[Boolean](actualResp.body.compile.toVector.unsafeRunSync.isEmpty)( // Verify Response's body is empty.
        expected => actualResp.as[A].unsafeRunSync == expected)
    statusCheck && bodyCheck
  }
} 
Example 124
Source File: ScriptManagerTest.scala    From codepropertygraph   with Apache License 2.0 5 votes vote down vote up
package io.shiftleft.console.scripting

import better.files.File
import cats.effect.IO
import org.scalatest.{Inside, Matchers, WordSpec}

import io.shiftleft.codepropertygraph.Cpg
import io.shiftleft.console.scripting.ScriptManager.{ScriptCollections, ScriptDescription, ScriptDescriptions}

import java.nio.file.{FileSystemNotFoundException, NoSuchFileException, Path}

import scala.io.Source
import scala.util.Try

class ScriptManagerTest extends WordSpec with Matchers with Inside {

  private object TestScriptExecutor extends AmmoniteExecutor {
    override protected def predef: String = ""
    override def runScript(scriptPath: Path, parameters: Map[String, String], cpg: Cpg): IO[Any] = IO.fromTry(
      Try {
        val source = Source.fromFile(scriptPath.toFile)
        val result = source.getLines.mkString(System.lineSeparator())
        source.close()
        result
      }
    )
  }

  private object TestScriptManager extends ScriptManager(TestScriptExecutor)

  protected val DEFAULT_CPG_NAME: String = {
    if (File(".").name == "console") {
      (File("..") / "resources" / "testcode" / "cpgs" / "method" / "cpg.bin.zip").pathAsString
    } else {
      (File("resources") / "testcode" / "cpgs" / "method" / "cpg.bin.zip").pathAsString
    }
  }

  def withScriptManager(f: ScriptManager => Unit): Unit = {
    f(TestScriptManager)
  }

  "listing scripts" should {
    "be correct" in withScriptManager { scriptManager =>
      val scripts = scriptManager.scripts()
      val expected = List(
        ScriptCollections("general",
                          ScriptDescriptions(
                            "A collection of general purpose scripts.",
                            List(ScriptDescription("list-funcs.sc", "Lists all functions."))
                          )),
        ScriptCollections("java",
                          ScriptDescriptions(
                            "A collection of java-specific scripts.",
                            List(ScriptDescription("list-sl-ns.sc", "Lists all shiftleft namespaces."))
                          )),
        ScriptCollections("general/general_plus",
                          ScriptDescriptions(
                            "Even more general purpose scripts.",
                            List.empty
                          ))
      )

      scripts should contain theSameElementsAs expected
    }
  }

  "running scripts" should {
    "be correct when explicitly specifying a CPG" in withScriptManager { scriptManager =>
      val expected =
        """|@main def main() = {
           |  cpg.method.name.l
           |}""".stripMargin

      scriptManager.runScript("general/list-funcs.sc", Map.empty, Cpg.emptyCpg) shouldBe expected
    }

    "be correct when specifying a CPG filename" in withScriptManager { scriptManager =>
      val expected =
        """|@main def main() = {
           |  cpg.method.name.l
           |}""".stripMargin

      scriptManager.runScript("general/list-funcs.sc", Map.empty, DEFAULT_CPG_NAME) shouldBe expected
    }

    "throw an exception if the specified CPG can not be found" in withScriptManager { scriptManager =>
      intercept[FileSystemNotFoundException] {
        scriptManager.runScript("general/list-funcs.sc", Map.empty, "cake.bin.zip")
      }
    }

    "throw an exception if the specified script can not be found" in withScriptManager { scriptManager =>
      intercept[NoSuchFileException] {
        scriptManager.runScript("list-funcs.sc", Map.empty, Cpg.emptyCpg)
      }
    }
  }

} 
Example 125
Source File: ResilientStreamSpec.scala    From fs2-rabbit   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.fs2rabbit.resiliency

import cats.effect.IO
import cats.effect.concurrent.Ref
import cats.implicits._
import dev.profunktor.fs2rabbit.BaseSpec
import fs2._

import scala.concurrent.duration._
import org.scalatest.compatible.Assertion

class ResilientStreamSpec extends BaseSpec {

  private val sink: Pipe[IO, Int, Unit] = _.evalMap(putStrLn)

  val emptyAssertion: Assertion = true shouldBe true

  it should "run a stream until it's finished" in {
    val program = Stream(1, 2, 3).covary[IO].through(sink)
    ResilientStream.run(program).as(emptyAssertion).unsafeToFuture
  }

  it should "run a stream and recover in case of failure" in {
    val errorProgram = Stream.raiseError[IO](new Exception("on purpose")).through(sink)

    def p(ref: Ref[IO, Int]): Stream[IO, Unit] =
      errorProgram.handleErrorWith { t =>
        Stream.eval(ref.get) flatMap { n =>
          if (n == 0) Stream.eval(IO.unit)
          else Stream.eval(ref.update(_ - 1) *> IO.raiseError(t))
        }
      }

    Ref.of[IO, Int](2).flatMap(r => ResilientStream.run(p(r), 1.second)).as(emptyAssertion).unsafeToFuture
  }

} 
Example 126
Source File: RabbitSuite.scala    From fs2-rabbit   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.fs2rabbit.interpreter

import cats.effect.{ContextShift, IO}
import cats.implicits._
import dev.profunktor.fs2rabbit.BaseSpec
import dev.profunktor.fs2rabbit.config.Fs2RabbitConfig

import scala.concurrent.ExecutionContext

class RabbitSuite extends BaseSpec with Fs2RabbitSpec {

  override implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)

  override val config: Fs2RabbitConfig =
    Fs2RabbitConfig(
      host = "localhost",
      port = 5672,
      virtualHost = "/",
      connectionTimeout = 30,
      ssl = false,
      username = "guest".some,
      password = "guest".some,
      requeueOnNack = false,
      requeueOnReject = false,
      internalQueueSize = 500.some
    )

} 
Example 127
Source File: EnvelopeDecoderSpec.scala    From fs2-rabbit   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.fs2rabbit.effects

import java.nio.charset.StandardCharsets

import cats.data.EitherT
import cats.effect.{IO, SyncIO}
import cats.instances.either._
import cats.instances.try_._
import dev.profunktor.fs2rabbit.model.{AmqpEnvelope, AmqpProperties, DeliveryTag, ExchangeName, RoutingKey}
import org.scalatest.funsuite.AsyncFunSuite

import scala.util.Try

class EnvelopeDecoderSpec extends AsyncFunSuite {

  // Available instances of EnvelopeDecoder for any ApplicativeError[F, Throwable]
  EnvelopeDecoder[Either[Throwable, ?], String]
  EnvelopeDecoder[SyncIO, String]
  EnvelopeDecoder[EitherT[IO, String, ?], String]
  EnvelopeDecoder[Try, String]

  test("should decode a UTF-8 string") {
    val msg = "hello world!"
    val raw = msg.getBytes(StandardCharsets.UTF_8)

    EnvelopeDecoder[IO, String]
      .run(
        AmqpEnvelope(DeliveryTag(0L), raw, AmqpProperties.empty, ExchangeName("test"), RoutingKey("test.route"), false))
      .flatMap { result =>
        IO(assert(result == msg))
      }
      .unsafeToFuture()
  }

  test("should decode payload with the given content encoding") {
    val msg = "hello world!"
    val raw = msg.getBytes(StandardCharsets.UTF_8)

    EnvelopeDecoder[IO, String]
      .run(
        AmqpEnvelope(DeliveryTag(0L),
                     raw,
                     AmqpProperties.empty.copy(contentEncoding = Some("UTF-16")),
                     ExchangeName("test"),
                     RoutingKey("test.route"),
                     false))
      .flatMap { result =>
        IO(assert(result != msg))
      }
      .unsafeToFuture()
  }

  test("should decode a UTF-16 string into a UTF-8 (default)") {
    val msg = "hello world!"
    val raw = msg.getBytes(StandardCharsets.UTF_16)

    EnvelopeDecoder[IO, String]
      .run(
        AmqpEnvelope(DeliveryTag(0L), raw, AmqpProperties.empty, ExchangeName("test"), RoutingKey("test.route"), false))
      .flatMap { result =>
        IO(assert(result != msg))
      }
      .unsafeToFuture()
  }

} 
Example 128
Source File: FileChecks.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.convert

import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path}

import cats.data.Kleisli
import cats.effect.IO
import fs2.{Pipe, Stream}
import docspell.common.MimeType
import docspell.convert.ConversionResult.Handler
import docspell.files.TikaMimetype

trait FileChecks {

  implicit class FileCheckOps(p: Path) {

    def isNonEmpty: Boolean =
      Files.exists(p) && Files.size(p) > 0

    def isType(mime: MimeType): Boolean =
      TikaMimetype.detect[IO](p).map(_ == mime).unsafeRunSync

    def isPDF: Boolean =
      isType(MimeType.pdf)

    def isPlainText: Boolean =
      isType(MimeType.text("plain"))
  }

  def storeFile(file: Path): Pipe[IO, Byte, Path] =
    in => Stream.eval(in.compile.to(Array).flatMap(bytes => IO(Files.write(file, bytes))))

  def storePdfHandler(file: Path): Handler[IO, Path] =
    storePdfTxtHandler(file, file.resolveSibling("unexpected.txt")).map(_._1)

  def storePdfTxtHandler(filePdf: Path, fileTxt: Path): Handler[IO, (Path, Path)] =
    Kleisli({
      case ConversionResult.SuccessPdfTxt(pdf, txt) =>
        for {
          pout <- pdf.through(storeFile(filePdf)).compile.lastOrError
          str  <- txt
          tout <- IO(Files.write(fileTxt, str.getBytes(StandardCharsets.UTF_8)))
        } yield (pout, tout)

      case ConversionResult.SuccessPdf(pdf) =>
        pdf.through(storeFile(filePdf)).compile.lastOrError.map(p => (p, fileTxt))

      case ConversionResult.Failure(ex) =>
        throw new Exception(s"Unexpected result (failure: ${ex.getMessage})", ex)

      case cr =>
        throw new Exception(s"Unexpected result: $cr")
    })

  def commandExists(cmd: String): Boolean =
    Runtime.getRuntime.exec(Array("which", cmd)).waitFor() == 0

} 
Example 129
Source File: TextExtractionSuite.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.extract.ocr

import cats.effect.IO
import docspell.common.Logger
import docspell.files.TestFiles
import minitest.SimpleTestSuite

object TextExtractionSuite extends SimpleTestSuite {
  import TestFiles._

  val logger = Logger.log4s[IO](org.log4s.getLogger)

  test("extract english pdf") {
    ignore()
    val text = TextExtract
      .extract[IO](letterSourceEN, blocker, logger, "eng", OcrConfig.default)
      .compile
      .lastOrError
      .unsafeRunSync()
    println(text)
  }

  test("extract german pdf") {
    ignore()
    val expect = TestFiles.letterDEText
    val extract = TextExtract
      .extract[IO](letterSourceDE, blocker, logger, "deu", OcrConfig.default)
      .compile
      .lastOrError
      .unsafeRunSync()

    assertEquals(extract.value, expect)
  }
} 
Example 130
Source File: TestFiles.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.files

import cats.effect.{Blocker, IO}
import fs2.Stream

import scala.concurrent.ExecutionContext

object TestFiles {
  val blocker     = Blocker.liftExecutionContext(ExecutionContext.global)
  implicit val CS = IO.contextShift(ExecutionContext.global)

  val letterSourceDE: Stream[IO, Byte] =
    ExampleFiles.letter_de_pdf
      .readURL[IO](8 * 1024, blocker)

  val letterSourceEN: Stream[IO, Byte] =
    ExampleFiles.letter_en_pdf
      .readURL[IO](8 * 1024, blocker)

  lazy val letterDEText =
    ExampleFiles.letter_de_txt
      .readText[IO](8 * 1024, blocker)
      .unsafeRunSync

  lazy val letterENText =
    ExampleFiles.letter_en_txt
      .readText[IO](8 * 1024, blocker)
      .unsafeRunSync
} 
Example 131
Source File: Playing.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.files

import cats.effect.{Blocker, ExitCode, IO, IOApp}
import docspell.common.MimeTypeHint

import scala.concurrent.ExecutionContext

object Playing extends IOApp {
  val blocker = Blocker.liftExecutionContext(ExecutionContext.global)

  def run(args: List[String]): IO[ExitCode] =
    IO {
      //val ods = ExampleFiles.examples_sample_ods.readURL[IO](8192, blocker)
      //val odt = ExampleFiles.examples_sample_odt.readURL[IO](8192, blocker)
      val rtf = ExampleFiles.examples_sample_rtf.readURL[IO](8192, blocker)

      val x = for {
        odsm1 <-
          TikaMimetype
            .detect(
              rtf,
              MimeTypeHint.filename(ExampleFiles.examples_sample_rtf.path.segments.last)
            )
        odsm2 <- TikaMimetype.detect(rtf, MimeTypeHint.none)
      } yield (odsm1, odsm2)
      println(x.unsafeRunSync())
      ExitCode.Success
    }
} 
Example 132
Source File: ImageSizeTest.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.files

import cats.implicits._
import cats.effect.{Blocker, IO}
import minitest.SimpleTestSuite

import scala.concurrent.ExecutionContext
import scala.util.Using

object ImageSizeTest extends SimpleTestSuite {
  val blocker     = Blocker.liftExecutionContext(ExecutionContext.global)
  implicit val CS = IO.contextShift(ExecutionContext.global)

  //tiff files are not supported on the jdk by default
  //requires an external library
  val files = List(
    ExampleFiles.camera_letter_en_jpg -> Dimension(1695, 2378),
    ExampleFiles.camera_letter_en_png -> Dimension(1695, 2378),
//    ExampleFiles.camera_letter_en_tiff -> Dimension(1695, 2378),
    ExampleFiles.scanner_jfif_jpg    -> Dimension(2480, 3514),
    ExampleFiles.bombs_20K_gray_jpeg -> Dimension(20000, 20000),
    ExampleFiles.bombs_20K_gray_png  -> Dimension(20000, 20000),
    ExampleFiles.bombs_20K_rgb_jpeg  -> Dimension(20000, 20000),
    ExampleFiles.bombs_20K_rgb_png   -> Dimension(20000, 20000)
  )

  test("get sizes from input-stream") {
    files.foreach {
      case (uri, expect) =>
        val url = uri.toJavaUrl.fold(sys.error, identity)
        Using.resource(url.openStream()) { in =>
          val dim = ImageSize.get(in)
          assertEquals(dim, expect.some)
        }
    }
  }

  test("get sizes from stream") {
    files.foreach {
      case (uri, expect) =>
        val stream = uri.readURL[IO](8192, blocker)
        val dim    = ImageSize.get(stream).unsafeRunSync()
        assertEquals(dim, expect.some)
    }
  }
} 
Example 133
Source File: JsonLogEncodingTest.scala    From cedi-dtrace   with Apache License 2.0 5 votes vote down vote up
package com.ccadllc.cedi.dtrace
package logging

import cats.effect.{ IO, Effect }

import io.circe._
import io.circe.syntax._

import org.scalacheck.Arbitrary

import org.scalatest.wordspec.AnyWordSpec

import json.encoding._

class JsonLogEncodingTests extends AnyWordSpec with TestSupport {

  // format: OFF
  val calculateQuarterlySalesTraceContextJson = Json.obj(
    "where"           -> calculateQuarterlySalesTraceContext.system.data.allValues.asJson,
    "root"            -> calculateQuarterlySalesTraceContext.currentSpan.root.asJson,
    "trace-id"        -> calculateQuarterlySalesTraceContext.currentSpan.spanId.traceId.asJson,
    "span-id"         -> calculateQuarterlySalesTraceContext.currentSpan.spanId.spanId.asJson,
    "parent-id"       -> calculateQuarterlySalesTraceContext.currentSpan.spanId.parentSpanId.asJson,
    "span-name"       -> calculateQuarterlySalesTraceContext.currentSpan.spanName.value.asJson,
    "start-time"      -> calculateQuarterlySalesTraceContext.currentSpan.startTime.asJson,
    "span-success"    -> calculateQuarterlySalesTraceContext.currentSpan.failure.isEmpty.asJson,
    "failure-detail"  -> calculateQuarterlySalesTraceContext.currentSpan.failure.map(_.render).asJson,
    "span-duration"   -> calculateQuarterlySalesTraceContext.currentSpan.duration.toMicros.asJson,
    "notes"           -> Map(
      quarterlySalesUnitsNote.name.value        ->  quarterlySalesUnitsNoteValue.value.toString,
      quarterlySalesGoalReachedNote.name.value  ->  quarterlySalesGoalReachedNoteValue.value.toString,
      salesRegionNote.name.value                ->  salesRegionNoteValue.value,
      quarterlySalesTotalNote.name.value        ->  quarterlySalesTotalNoteValue.value.toString
    ).asJson
  )
  // format: ON

  implicit def traceArb[F[_]: Effect]: Arbitrary[TraceContext[F]] = Arbitrary(genTraceContext[F])

  "Trace" should { encodeGeneratedJson[TraceContext[IO]] }
  "Trace" should { encodeSpecificJson(calculateQuarterlySalesTraceContext, calculateQuarterlySalesTraceContextJson) }
} 
Example 134
Source File: TestSupport.scala    From cedi-dtrace   with Apache License 2.0 5 votes vote down vote up
package com.ccadllc.cedi.dtrace
package logging

import cats.effect.{ IO, Sync }

import io.circe._
import io.circe.syntax._

import org.scalacheck.Arbitrary

import org.scalatest.Suite
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks

import shapeless.Lazy

trait TestSupport extends AnyWordSpecLike with Matchers with ScalaCheckPropertyChecks with TraceGenerators with TestData {
  self: Suite =>

  override def testEmitter[F[_]: Sync]: F[TraceSystem.Emitter[F]] = Sync[F].pure(LogEmitter.apply)

  val salesManagementSystem = TraceSystem(testSystemData, testEmitter[IO].unsafeRunSync, TraceSystem.realTimeTimer[IO])
  val calculateQuarterlySalesTraceContext = TraceContext(quarterlySalesCalculationSpan, true, salesManagementSystem)

  def encodeGeneratedJson[A: Arbitrary](implicit encoder: Lazy[Encoder[A]]): Unit = {
    implicit val e = encoder.value
    "encode arbitrary instances to JSON" in {
      forAll { (msg: A) => msg.asJson.noSpaces should not be (None) }
    }
  }
  def encodeSpecificJson[A](a: A, json: Json)(implicit encoder: Lazy[Encoder[A]]): Unit = {
    implicit val e = encoder.value
    "encode specific instance to JSON and ensure it matches expected" in { a.asJson shouldBe json }
  }
} 
Example 135
Source File: LogstashLogbackEmitterTest.scala    From cedi-dtrace   with Apache License 2.0 5 votes vote down vote up
package com.ccadllc.cedi.dtrace
package logstash

import cats.effect.IO
import org.scalatest.wordspec.AnyWordSpec

class LogstashLogbackEmitterTest extends AnyWordSpec with TestData {
  "LogstashLogbackEmitterTest" should {
    "work" in {
      val system = TraceSystem(testSystemData, new LogstashLogbackEmitter[IO], quarterlySalesCalculationTimer)
      val spanRoot = Span.root[IO](quarterlySalesCalculationTimer, Span.Name("calculate-quarterly-sales")).unsafeRunSync
      IO.unit.toTraceT.trace(TraceContext(spanRoot, true, system)).unsafeRunSync
    }
  }
} 
Example 136
Source File: EcsLogstashLogbackEmitterTest.scala    From cedi-dtrace   with Apache License 2.0 5 votes vote down vote up
package com.ccadllc.cedi.dtrace
package logstash

import cats.effect.IO
import org.scalatest.wordspec.AnyWordSpec

class EcsLogstashLogbackEmitterTest extends AnyWordSpec with TestData {
  "EcsLogstashLogbackEmitterTest" should {
    "work" in {
      val system = TraceSystem(testSystemData, new EcsLogstashLogbackEmitter[IO], quarterlySalesCalculationTimer)
      val spanRoot = Span.root[IO](quarterlySalesCalculationTimer, Span.Name("calculate-quarterly-sales")).unsafeRunSync
      IO.unit.toTraceT.trace(TraceContext(spanRoot, true, system)).unsafeRunSync
    }
  }
} 
Example 137
Source File: CodeGenWrite.scala    From bosatsu   with Apache License 2.0 5 votes vote down vote up
package org.bykn.bosatsu

import cats.data.NonEmptyList
import cats.effect.IO
import java.nio.file.Path
import java.io.PrintWriter
import org.typelevel.paiges.Doc

object CodeGenWrite {
  @annotation.tailrec
  final def toPath(root: Path, pn: PackageName): Path =
    pn.parts match {
      case NonEmptyList(h, Nil) => root.resolve(h).resolve("Values.java")
      case NonEmptyList(h0, h1 :: tail) =>
        toPath(root.resolve(h0), PackageName(NonEmptyList(h1, tail)))
    }

  def writeDoc(p: Path, d: Doc): IO[Unit] =
    IO {
      Option(p.getParent).foreach(_.toFile.mkdirs)
      val pw = new PrintWriter(p.toFile, "UTF-8")
      try d.renderStream(80).foreach(pw.print(_))
      finally {
        pw.close
      }
    }
} 
Example 138
Source File: Commands.scala    From franklin   with Apache License 2.0 5 votes vote down vote up
package com.azavea.franklin.api.commands

import cats.effect.{ContextShift, ExitCode, IO}
import cats.implicits._
import com.azavea.franklin.crawler.StacImport
import com.monovore.decline._
import doobie.Transactor
import doobie.free.connection.{rollback, setAutoCommit, unit}
import doobie.util.transactor.Strategy
import org.flywaydb.core.Flyway

object Commands {

  final case class RunMigrations(databaseConfig: DatabaseConfig)

  final case class RunServer(apiConfig: ApiConfig, dbConfig: DatabaseConfig)

  final case class RunImport(
      catalogRoot: String,
      config: DatabaseConfig,
      dryRun: Boolean
  )

  private def runImportOpts(implicit cs: ContextShift[IO]): Opts[RunImport] =
    Opts.subcommand("import", "Import a STAC catalog") {
      (
        Options.catalogRoot,
        Options.databaseConfig,
        Options.dryRun
      ).mapN(RunImport)
    }

  private def runMigrationsOpts(implicit cs: ContextShift[IO]): Opts[RunMigrations] =
    Opts.subcommand("migrate", "Runs migrations against database") {
      Options.databaseConfig map RunMigrations
    }

  private def runServerOpts(implicit cs: ContextShift[IO]): Opts[RunServer] =
    Opts.subcommand("serve", "Runs web service") {
      (Options.apiConfig, Options.databaseConfig) mapN RunServer
    }

  def runMigrations(dbConfig: DatabaseConfig): IO[ExitCode] = IO {
    Flyway
      .configure()
      .dataSource(
        s"${dbConfig.jdbcUrl}",
        dbConfig.dbUser,
        dbConfig.dbPass
      )
      .locations("classpath:migrations/")
      .load()
      .migrate()
    ExitCode.Success
  }

  def runImport(
      stacCatalog: String,
      config: DatabaseConfig,
      dryRun: Boolean
  )(
      implicit contextShift: ContextShift[IO]
  ): IO[Unit] = {
    val xa =
      Transactor.strategy.set(
        Transactor.fromDriverManager[IO](
          config.driver,
          config.jdbcUrl,
          config.dbUser,
          config.dbPass
        ),
        if (dryRun) {
          Strategy.default.copy(before = setAutoCommit(false), after = rollback, always = unit)
        } else { Strategy.default }
      )

    new StacImport(stacCatalog).runIO(xa)
  }

  def applicationCommand(implicit cs: ContextShift[IO]): Command[Product] =
    Command("", "Your Friendly Neighborhood OGC API - Features and STAC Web Service") {
      runServerOpts orElse runMigrationsOpts orElse runImportOpts
    }

} 
Example 139
Source File: CogAssetNodeImplicits.scala    From franklin   with Apache License 2.0 5 votes vote down vote up
package com.azavea.franklin.tile

import cats.effect.ContextShift
import cats.effect.IO
import geotrellis.proj4.WebMercator
import geotrellis.raster._
import geotrellis.raster.io.geotiff.AutoHigherResolution
import geotrellis.raster.resample.NearestNeighbor
import geotrellis.server.ExtentReification
import geotrellis.server.TmsReification
import geotrellis.vector.Extent

object CogAssetNodeImplicits extends TileUtil {

  implicit def cogAssetNodeTmsReification: TmsReification[CogAssetNode] =
    new TmsReification[CogAssetNode] {

      def tmsReification(
          self: CogAssetNode,
          buffer: Int
      )(implicit cs: ContextShift[IO]): (Int, Int, Int) => IO[ProjectedRaster[MultibandTile]] =
        (z: Int, x: Int, y: Int) => {
          def fetch(xCoord: Int, yCoord: Int): IO[Raster[MultibandTile]] = {
            self.fetchTile(z, xCoord, yCoord, WebMercator).flatMap(a => IO(a))
          }

          fetch(x, y).map { tile =>
            val extent = tmsLevels(z).mapTransform.keyToExtent(x, y)
            ProjectedRaster(tile.tile, extent, WebMercator)
          }
        }
    }

  implicit def cogAssetNodeExtentReification: ExtentReification[CogAssetNode] =
    new ExtentReification[CogAssetNode] {

      def extentReification(
          self: CogAssetNode
      )(implicit cs: ContextShift[IO]): (Extent, CellSize) => IO[ProjectedRaster[MultibandTile]] =
        (extent: Extent, cs: CellSize) => {
          self.getRasterSource map { rs =>
            rs.resample(
                TargetRegion(new GridExtent[Long](extent, cs)),
                NearestNeighbor,
                AutoHigherResolution
              )
              .read(extent)
              .map { ProjectedRaster(_, WebMercator) } match {
              case Some(mbt) => mbt
              case _ =>
                throw new Exception(
                  s"No tile available for RasterExtent: ${RasterExtent(extent, cs)}"
                )
            }
          }
        }
    }
} 
Example 140
Source File: CogAssetNode.scala    From franklin   with Apache License 2.0 5 votes vote down vote up
package com.azavea.franklin.tile

import cats.data.{NonEmptyList => NEL}
import cats.effect.IO
import com.azavea.franklin.cache._
import com.azavea.stac4s.StacItemAsset
import geotrellis.layer._
import geotrellis.proj4._
import geotrellis.raster._
import geotrellis.raster.geotiff.GeoTiffRasterSource
import geotrellis.raster.histogram.Histogram
import geotrellis.raster.resample._
import scalacache.modes.sync._
import scalacache.{Sync => _, _}

case class CogAssetNode(asset: StacItemAsset, bands: Seq[Int]) extends TileUtil {
  private val histoKey = s"histogram - $bands - ${asset.href}"
  private val tiffKey  = s"tiff - ${asset.href}"

  def getRasterSource: IO[GeoTiffRasterSource] = {

    val infoFromCache = sync.get[SerializableGeotiffInfo](tiffKey)
    val tiffInfoIO = infoFromCache match {
      case Some(info) => IO.pure(info)
      case _ => {
        for {
          info <- IO.delay(GeotiffReader.getGeotiffInfo(asset.href))
        } yield {
          sync.put(tiffKey)(info)
          info
        }
      }
    }

    for {
      info <- tiffInfoIO
      byteReader = getByteReader(asset.href)
      gtiffInfo  = info.toGeotiffInfo(byteReader)
      geotiff    = GeotiffReader.readMultibandWithInfo(gtiffInfo)
    } yield GeoTiffRasterSource(asset.href, baseTiff = Some(geotiff))
  }

  def getHistograms: IO[List[Histogram[Int]]] = {
    val histogramFromSource = getRasterSource.map { rs =>
      val overviews = rs.tiff.overviews
      val smallestOverview = overviews.maxBy { overview =>
        val cs = overview.cellSize
        cs.width
      }
      bands.map { b => smallestOverview.tile.band(b).histogram }.toList
    }

    sync.get[List[Histogram[Int]]](histoKey) match {
      case Some(histograms) => IO.pure(histograms)
      case _ => {
        for {
          histograms <- histogramFromSource
          _          <- IO.pure(sync.put(histoKey)(histograms, None))
        } yield histograms
      }
    }
  }

  def getRasterExtents: IO[NEL[RasterExtent]] = {
    getRasterSource map { rs =>
      NEL
        .fromList(rs.resolutions.map { cs => RasterExtent(rs.extent, cs) })
        .getOrElse(NEL(rs.gridExtent.toRasterExtent, Nil))
    }
  }

  def fetchTile(
      zoom: Int,
      x: Int,
      y: Int,
      crs: CRS = WebMercator,
      method: ResampleMethod = NearestNeighbor,
      target: ResampleTarget = DefaultTarget
  ): IO[Raster[MultibandTile]] = {
    getRasterSource map { rs =>
      val key              = SpatialKey(x, y)
      val layoutDefinition = tmsLevels(zoom)
      val rasterSource =
        rs.reproject(crs, target).tileToLayout(layoutDefinition, method)
      rasterSource.read(key, bands).map(Raster(_, layoutDefinition.mapTransform(key)))
    } map {
      case Some(t) =>
        t
      case _ =>
        invisiRaster
    }
  }

} 
Example 141
Source File: CollectionsServiceSpec.scala    From franklin   with Apache License 2.0 5 votes vote down vote up
package com.azavea.franklin.api.services

import cats.data.OptionT
import cats.effect.IO
import cats.implicits._
import com.azavea.franklin.Generators
import com.azavea.franklin.api.{TestClient, TestServices}
import com.azavea.franklin.database.TestDatabaseSpec
import com.azavea.franklin.datamodel.CollectionsResponse
import com.azavea.stac4s.StacCollection
import com.azavea.stac4s.testing._
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.{Method, Request, Uri}
import org.specs2.{ScalaCheck, Specification}

import java.net.URLEncoder
import java.nio.charset.StandardCharsets

class CollectionsServiceSpec
    extends Specification
    with ScalaCheck
    with TestDatabaseSpec
    with Generators {
  def is = s2"""
  This specification verifies that the collections service can run without crashing

  The collections service should:
    - create and delete collections $createDeleteCollectionExpectation
    - list collections              $listCollectionsExpectation
    - get collections by id         $getCollectionsExpectation
"""

  val testServices: TestServices[IO] = new TestServices[IO](transactor)

  val testClient: TestClient[IO] =
    new TestClient[IO](testServices.collectionsService, testServices.collectionItemsService)

  def listCollectionsExpectation = prop {
    (stacCollectionA: StacCollection, stacCollectionB: StacCollection) =>
      {
        val listIO = (
          testClient.getCollectionResource(stacCollectionA),
          testClient.getCollectionResource(stacCollectionB)
        ).tupled use { _ =>
          val request = Request[IO](method = Method.GET, Uri.unsafeFromString(s"/collections"))
          (for {
            resp    <- testServices.collectionsService.routes.run(request)
            decoded <- OptionT.liftF { resp.as[CollectionsResponse] }
          } yield decoded).value
        }

        val result = listIO.unsafeRunSync.get.collections map { _.id }

        (result must contain(stacCollectionA.id)) and (result must contain(stacCollectionB.id))
      }
  }

  def getCollectionsExpectation = prop { (stacCollection: StacCollection) =>
    val fetchIO =
      testClient.getCollectionResource(stacCollection) use { collection =>
        val encodedId = URLEncoder.encode(collection.id, StandardCharsets.UTF_8.toString)
        val request =
          Request[IO](method = Method.GET, Uri.unsafeFromString(s"/collections/$encodedId"))
        (for {
          resp    <- testServices.collectionsService.routes.run(request)
          decoded <- OptionT.liftF { resp.as[StacCollection] }
        } yield (decoded, collection)).value
      }

    val (fetched, inserted) = fetchIO.unsafeRunSync.get

    fetched must beTypedEqualTo(inserted)
  }

  // since creation / deletion is a part of the collection resource, and accurate creation is checked
  // in getCollectionsExpectation, this test just makes sure that if other tests are failing, it's
  // not because create/delete are broken
  def createDeleteCollectionExpectation = prop { (stacCollection: StacCollection) =>
    (testClient
      .getCollectionResource(stacCollection) use { _ => IO.unit }).unsafeRunSync must beTypedEqualTo(
      ()
    )
  }

} 
Example 142
Source File: TracerSpec.scala    From http4s-tracer   with Apache License 2.0 5 votes vote down vote up
package dev.profunktor.tracer

import cats.effect.IO
import dev.profunktor.tracer.instances.tracerlog._
import munit.FunSuite
import org.http4s._
import org.http4s.client.dsl.io._
import org.http4s.dsl.io._
import org.http4s.implicits._

class TracerSpec extends FunSuite {

  // format: off
  override def munitValueTransforms =
    super.munitValueTransforms :+ new ValueTransform("IO", {
      case ioa: IO[_] => IO.suspend(ioa).unsafeToFuture
    })
  // format: on

  val customHeaderName  = "Test-Id"
  val customHeaderValue = "my-custom-value"

  val tracer: Tracer[IO]       = Tracer.create[IO]()
  val customTracer: Tracer[IO] = Tracer.create[IO](customHeaderName)

  val tracerApp: HttpApp[IO]       = tracer.middleware(TestHttpRoute.routes(tracer).orNotFound)
  val customTracerApp: HttpApp[IO] = customTracer.middleware(TestHttpRoute.routes(customTracer).orNotFound)

  def defaultAssertion(traceHeaderName: String): Response[IO] => IO[Unit] =
    resp =>
      IO {
        assert(resp.status == Status.Ok)
        assert(resp.headers.toList.map(_.name.value).contains(traceHeaderName))
    }

  def customAssertion(traceHeaderName: String): Response[IO] => IO[Unit] =
    resp =>
      IO {
        assert(resp.status == Status.Ok)
        assert(resp.headers.toList.map(_.name.value).contains(traceHeaderName))
        assert(resp.headers.toList.map(_.value).contains(customHeaderValue))
    }

  test("Default TraceId header is created") {
    for {
      req  <- GET(Uri.uri("/"))
      resp <- tracerApp(req)
      _    <- defaultAssertion(Tracer.DefaultTraceIdHeader)(resp)
    } yield ()
  }

  test("TraceId header is passed in the request (no TraceId created)") {
    for {
      req  <- GET(Uri.uri("/"), Header(Tracer.DefaultTraceIdHeader, customHeaderValue))
      resp <- tracerApp(req)
      _    <- customAssertion(Tracer.DefaultTraceIdHeader)(resp)
    } yield ()
  }

  test("Custom TraceId header (Test-Id) is created") {
    for {
      req  <- GET(Uri.uri("/"))
      resp <- customTracerApp(req)
      _    <- defaultAssertion(customHeaderName)(resp)
    } yield ()
  }

  test("TraceId header (Test-Id) is passed in the request") {
    for {
      req  <- GET(Uri.uri("/"), Header(customHeaderName, customHeaderValue))
      resp <- customTracerApp(req)
      _    <- customAssertion(customHeaderName)(resp)
    } yield ()
  }

}

object TestHttpRoute extends Http4sTracerDsl[IO] {
  def routes(implicit t: Tracer[IO]): HttpRoutes[IO] =
    TracedHttpRoute[IO] {
      case GET -> Root using traceId =>
        Ok(traceId.value)
    }
} 
Example 143
Source File: CommandExecutor.scala    From renku   with Apache License 2.0 5 votes vote down vote up
package ch.renku.acceptancetests.tooling.console

import java.io.{File, InputStream}
import java.nio.file.Path
import java.util
import java.util.concurrent.ConcurrentLinkedQueue

import cats.effect.IO
import cats.implicits._
import ch.renku.acceptancetests.model.users.UserCredentials
import ch.renku.acceptancetests.tooling.TestLogger.logger
import ch.renku.acceptancetests.tooling.console.Command.UserInput

import scala.jdk.CollectionConverters._
import scala.language.postfixOps
import scala.sys.process._

private class CommandExecutor(command: Command) {

  def execute(implicit workPath: Path, userCredentials: UserCredentials): String = {

    implicit val output: util.Collection[String] = new ConcurrentLinkedQueue[String]()

    IO {
      executeCommand
      output.asString
    } recoverWith consoleException
  }.unsafeRunSync()

  def safeExecute(implicit workPath: Path, userCredentials: UserCredentials): String = {
    implicit val output: util.Collection[String] = new ConcurrentLinkedQueue[String]()

    IO {
      executeCommand
      output.asString
    } recover outputAsString
  }.unsafeRunSync()

  private def executeCommand(implicit workPath: Path,
                             output:            util.Collection[String],
                             userCredentials:   UserCredentials): Unit =
    command.userInputs.foldLeft(buildProcess) { (process, userInput) =>
      process #< userInput.asStream
    } lazyLines ProcessLogger(logLine _) foreach logLine

  private def buildProcess(implicit workPath: Path) =
    command.maybeFileName.foldLeft(Process(command.toString.stripMargin, workPath.toFile)) { (process, fileName) =>
      process #>> new File(workPath.toUri resolve fileName.value)
    }

  private def logLine(
      line:          String
  )(implicit output: util.Collection[String], userCredentials: UserCredentials): Unit = line.trim match {
    case "" => ()
    case line =>
      val obfuscatedLine = line.replace(userCredentials.password.value, "###")
      output add obfuscatedLine
      logger debug obfuscatedLine
  }

  private def consoleException(implicit output: util.Collection[String]): PartialFunction[Throwable, IO[String]] = {
    case _ =>
      ConsoleException {
        s"$command failed with:\n${output.asString}"
      }.raiseError[IO, String]
  }

  private def outputAsString(implicit output: util.Collection[String]): PartialFunction[Throwable, String] = {
    case _ => output.asString
  }

  private implicit class OutputOps(output: util.Collection[String]) {
    lazy val asString: String = output.asScala.mkString("\n")
  }

  private implicit class UserInputOps(userInput: UserInput) {
    import java.nio.charset.StandardCharsets.UTF_8

    lazy val asStream: InputStream = new java.io.ByteArrayInputStream(
      userInput.value.getBytes(UTF_8.name)
    )
  }
} 
Example 144
Source File: Main.scala    From cats-stm   with Apache License 2.0 5 votes vote down vote up
package io.github.timwspence.cats.stm

import cats.effect.{ExitCode, IO, IOApp}
//import io.github.timwspence.cats.stm.{TVar, STM}
import scala.concurrent.duration._

object Main extends IOApp {

  override def run(args: List[String]): IO[ExitCode] =
    for {
      accountForTim   <- TVar.of[Long](100).commit[IO]
      accountForSteve <- TVar.of[Long](0).commit[IO]
      _               <- printBalances(accountForTim, accountForSteve)
      _               <- giveTimMoreMoney(accountForTim).start
      _               <- transfer(accountForTim, accountForSteve)
      _               <- printBalances(accountForTim, accountForSteve)
    } yield ExitCode.Success

  private def transfer(accountForTim: TVar[Long], accountForSteve: TVar[Long]): IO[Unit] =
    STM.atomically[IO] {
      for {
        balance <- accountForTim.get
        _       <- STM.check(balance > 100)
        _       <- accountForTim.modify(_ - 100)
        _       <- accountForSteve.modify(_ + 100)
      } yield ()
    }

  private def giveTimMoreMoney(accountForTim: TVar[Long]): IO[Unit] =
    for {
      _ <- IO.sleep(5000.millis)
      _ <- STM.atomically[IO](accountForTim.modify(_ + 1))
    } yield ()

  private def printBalances(accountForTim: TVar[Long], accountForSteve: TVar[Long]): IO[Unit] =
    for {
      (amountForTim, amountForSteve) <- STM.atomically[IO](for {
        t <- accountForTim.get
        s <- accountForSteve.get
      } yield (t, s))
      _ <- IO(println(s"Tim: $amountForTim"))
      _ <- IO(println(s"Steve: $amountForSteve"))
    } yield ()

} 
Example 145
Source File: TVarTest.scala    From cats-stm   with Apache License 2.0 5 votes vote down vote up
package io.github.timwspence.cats.stm

import cats.effect.{ContextShift, IO, Timer}
import org.scalatest.matchers.should.Matchers
import org.scalatest.funsuite.AsyncFunSuite 

import scala.concurrent.ExecutionContext

class TVarTest extends AsyncFunSuite with Matchers {
  implicit override def executionContext: ExecutionContext = ExecutionContext.Implicits.global

  implicit val timer: Timer[IO] = IO.timer(executionContext)

  implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)

  test("Get returns current value") {
    val prog: STM[String] = for {
      tvar  <- TVar.of("hello")
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "hello"
    }
  }

  test("Set changes current value") {
    val prog: STM[String] = for {
      tvar  <- TVar.of("hello")
      _     <- tvar.set("world")
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "world"
      value shouldBe "world"
    }
  }

  test("Modify changes current value") {
    val prog: STM[String] = for {
      tvar  <- TVar.of("hello")
      _     <- tvar.modify(_.toUpperCase)
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "HELLO"
    }
  }

  test("Pending transaction is removed on success") {
    val tvar = TVar.of("foo").commit[IO].unsafeRunSync

    val prog: STM[String] = for {
      _     <- tvar.modify(_.toUpperCase)
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "FOO"

      tvar.value shouldBe "FOO"
      tvar.pending.get.isEmpty shouldBe true
    }
  }

  test("Pending transaction is removed on failure") {
    val tvar = TVar.of("foo").commit[IO].unsafeRunSync

    val prog: STM[String] = for {
      _     <- tvar.modify(_.toUpperCase)
      _     <- STM.abort[String](new RuntimeException("boom"))
      value <- tvar.get
    } yield value

    for (_ <- prog.commit[IO].attempt.unsafeToFuture) yield {
      tvar.value shouldBe "foo"

      tvar.pending.get.isEmpty shouldBe true
    }
  }
} 
Example 146
Source File: PropertyTests.scala    From cats-stm   with Apache License 2.0 5 votes vote down vote up
package io.github.timwspence.cats.stm

import cats.effect.{ContextShift, IO, Timer}
import cats.instances.list._
import cats.syntax.functor._
import cats.syntax.traverse._
import org.scalacheck._
import org.scalatest.matchers.should.Matchers
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks

import scala.concurrent.ExecutionContext
import scala.util.Random


class MaintainsInvariants extends AnyFunSuite with ScalaCheckDrivenPropertyChecks with Matchers {
  implicit val executionContext: ExecutionContext = ExecutionContext.Implicits.global

  implicit val timer: Timer[IO] = IO.timer(executionContext)

  implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)

  val tvarGen: Gen[TVar[Long]] = for {
    value <- Gen.posNum[Long]
  } yield TVar.of(value).commit[IO].unsafeRunSync

  val txnGen: List[TVar[Long]] => Gen[STM[Unit]] = tvars =>
    for {
      fromIdx <- Gen.choose(0, tvars.length - 1)
      toIdx   <- Gen.choose(0, tvars.length - 1) suchThat (_ != fromIdx)
      txn <- for {
        balance <- tvars(fromIdx).get
        transfer = Math.abs(Random.nextLong()) % balance
        _ <- tvars(fromIdx).modify(_ - transfer)
        _ <- tvars(toIdx).modify(_ + transfer)
      } yield ()
    } yield txn

  val gen: Gen[(Long, List[TVar[Long]], IO[Unit])] = for {
    tvars <- Gen.listOfN(50, tvarGen)
    total = tvars.foldLeft(0L)((acc, tvar) => acc + tvar.value)
    txns <- Gen.listOf(txnGen(tvars))
    commit = txns.traverse(_.commit[IO].start)
    run    = commit.flatMap(l => l.traverse(_.join)).void
  } yield (total, tvars, run)

  test("Transactions maintain invariants") {
    forAll(gen) { g =>
      val total = g._1
      val tvars = g._2
      val txn   = g._3

      txn.unsafeRunSync()

      tvars.map(_.value).sum shouldBe total
    }
  }

} 
Example 147
Source File: TQueueTest.scala    From cats-stm   with Apache License 2.0 5 votes vote down vote up
package io.github.timwspence.cats.stm

import cats.effect.{ContextShift, IO, Timer}
import cats.instances.string._
import cats.syntax.semigroup._
import org.scalatest.matchers.should.Matchers
import org.scalatest.funsuite.AsyncFunSuite 

import scala.concurrent.ExecutionContext

class TQueueTest extends AsyncFunSuite with Matchers {
  implicit override def executionContext: ExecutionContext = ExecutionContext.Implicits.global

  implicit val timer: Timer[IO] = IO.timer(executionContext)

  implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)

  test("Read removes the first element") {
    val prog: STM[(String, Boolean)] = for {
      tqueue <- TQueue.empty[String]
      _      <- tqueue.put("hello")
      value  <- tqueue.read
      empty  <- tqueue.isEmpty
    } yield value -> empty

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value._1 shouldBe "hello"
      value._2 shouldBe true
    }
  }

  test("Peek does not remove the first element") {
    val prog: STM[(String, Boolean)] = for {
      tqueue <- TQueue.empty[String]
      _      <- tqueue.put("hello")
      value  <- tqueue.peek
      empty  <- tqueue.isEmpty
    } yield value -> empty

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value._1 shouldBe "hello"
      value._2 shouldBe false
    }
  }

  test("TQueue is FIFO") {
    val prog: STM[String] = for {
      tqueue <- TQueue.empty[String]
      _      <- tqueue.put("hello")
      _      <- tqueue.put("world")
      hello  <- tqueue.read
      world  <- tqueue.peek
    } yield hello |+| world

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "helloworld"
    }
  }

} 
Example 148
Source File: TSemaphoreTest.scala    From cats-stm   with Apache License 2.0 5 votes vote down vote up
package io.github.timwspence.cats.stm

import cats.effect.{ContextShift, IO, Timer}
import org.scalatest.matchers.should.Matchers
import org.scalatest.funsuite.AsyncFunSuite

import scala.concurrent.ExecutionContext

class TSemaphoreTest extends AsyncFunSuite with Matchers {
  implicit override def executionContext: ExecutionContext = ExecutionContext.Implicits.global

  implicit val timer: Timer[IO] = IO.timer(executionContext)

  implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)

  test("Acquire decrements the number of permits") {
    val prog: STM[Long] = for {
      tsem  <- TSemaphore.make(1)
      _     <- tsem.acquire
      value <- tsem.available
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe 0
    }
  }

  test("Release increments the number of permits") {
    val prog: STM[Long] = for {
      tsem  <- TSemaphore.make(0)
      _     <- tsem.release
      value <- tsem.available
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe 1
    }
  }

} 
Example 149
Source File: AkkaActorIntermediator.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.sourcing.akka

import akka.actor.ActorSystem
import akka.pattern.ask
import akka.util.Timeout
import cats.effect.{ContextShift, Effect, IO, Timer}
import cats.syntax.all._
import ch.epfl.bluebrain.nexus.sourcing.akka.Msg._
import retry.CatsEffect._
import retry.syntax.all._
import retry.{RetryDetails, RetryPolicy}

import scala.reflect.ClassTag


abstract private[akka] class AkkaActorIntermediator[F[_]: Timer](
    name: String,
    selection: ActorRefSelection[F],
    askTimeout: Timeout
)(implicit F: Effect[F], as: ActorSystem, policy: RetryPolicy[F]) {

  implicit private[akka] val contextShift: ContextShift[IO]        = IO.contextShift(as.dispatcher)
  implicit private[akka] def noop[A]: (A, RetryDetails) => F[Unit] = retry.noop[F, A]
  implicit private val timeout: Timeout                            = askTimeout

  private[akka] def send[M <: Msg, Reply, A](id: String, msg: M, f: Reply => A)(implicit
      Reply: ClassTag[Reply]
  ): F[A] =
    selection(name, id).flatMap { ref =>
      val future = IO(ref ? msg)
      val fa     = IO.fromFuture(future).to[F]
      fa.flatMap[A] {
          case Reply(value)                     => F.pure(f(value))
          case te: TypeError                    => F.raiseError(te)
          case um: UnexpectedMsgId              => F.raiseError(um)
          case cet: CommandEvaluationTimeout[_] => F.raiseError(cet)
          case cee: CommandEvaluationError[_]   => F.raiseError(cee)
          case other                            => F.raiseError(TypeError(id, Reply.runtimeClass.getSimpleName, other))
        }
        .retryingOnAllErrors[Throwable]
    }
} 
Example 150
Source File: InMemoryAggregateSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.sourcing

import cats.effect.{ContextShift, IO, Timer}
import ch.epfl.bluebrain.nexus.sourcing.AggregateFixture._
import ch.epfl.bluebrain.nexus.sourcing.Command.{Increment, IncrementAsync, Initialize}
import ch.epfl.bluebrain.nexus.sourcing.Event.{Incremented, Initialized}
import ch.epfl.bluebrain.nexus.sourcing.State.Current

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class InMemoryAggregateSpec extends SourcingSpec {

  implicit val ctx: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit val timer: Timer[IO]      = IO.timer(ExecutionContext.global)

  "An InMemoryAggregate" should {

    val agg = Aggregate
      .inMemory[IO, Int]("global", initialState, AggregateFixture.next, AggregateFixture.evaluate[IO])
      .unsafeRunSync()

    "return its name" in {
      agg.name shouldEqual "global"
    }

    "update its state when accepting commands" in {
      agg.evaluateE(1, Increment(0, 2)).unsafeRunSync().rightValue shouldEqual Incremented(1, 2)
      agg
        .evaluate(1, IncrementAsync(1, 5, 200.millis))
        .unsafeRunSync()
        .rightValue shouldEqual (Current(2, 7) -> Incremented(2, 5))
      agg.currentState(1).unsafeRunSync() shouldEqual Current(2, 7)
    }

    "return its current seq nr" in {
      agg.lastSequenceNr(1).unsafeRunSync() shouldEqual 2L
    }

    "test without applying changes" in {
      agg.test(1, Initialize(0)).unsafeRunSync().leftValue
      agg.testE(1, Initialize(2)).unsafeRunSync().rightValue shouldEqual Initialized(3)
      agg.testS(1, Initialize(2)).unsafeRunSync().rightValue shouldEqual Current(3, 0)
      agg.currentState(1).unsafeRunSync() shouldEqual Current(2, 7)
    }

    "not update its state if evaluation fails" in {
      agg.evaluate(1, Initialize(0)).unsafeRunSync().leftValue
      agg.currentState(1).unsafeRunSync() shouldEqual Current(2, 7)
    }

    "evaluate commands one at a time" in {
      agg.evaluateS(1, Initialize(2)).unsafeRunSync().rightValue shouldEqual Current(3, 0)
      agg.currentState(1).unsafeRunSync() shouldEqual Current(3, 0)
      agg.evaluateS(1, IncrementAsync(3, 2, 300.millis)).unsafeToFuture()
      agg.evaluateE(1, IncrementAsync(4, 2, 20.millis)).unsafeRunSync().rightValue shouldEqual Incremented(5, 2)
      agg.currentState(1).unsafeRunSync() shouldEqual Current(5, 4)
    }

    "fold over the event stream in order" in {
      agg
        .foldLeft(1, (0, true)) {
          case ((lastRev, succeeded), event) => (event.rev, succeeded && event.rev - lastRev == 1)
        }
        .unsafeRunSync()
        ._2 shouldEqual true
    }

    "return all events" in {
      agg.foldLeft(1, 0) { case (acc, _) => acc + 1 }.unsafeRunSync() shouldEqual 5
    }

    "append events" in {
      agg.append(2, Incremented(1, 2)).unsafeRunSync() shouldEqual 1L
      agg.currentState(1).unsafeRunSync() shouldEqual Current(5, 4)
    }

    "return true for existing ids" in {
      agg.exists(1).unsafeRunSync() shouldEqual true
    }

    "return false for unknown ids" in {
      agg.exists(Int.MaxValue).unsafeRunSync() shouldEqual false
    }

    "return the sequence number for a snapshot" in {
      agg.snapshot(1).unsafeRunSync() shouldEqual 5L
    }

  }

} 
Example 151
Source File: ProjectionsSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.sourcing.projections

import akka.actor.ActorSystem
import akka.persistence.query.Offset
import akka.stream.scaladsl.Source
import akka.testkit.{TestKit, TestKitBase}
import cats.effect.{ContextShift, IO}
import ch.epfl.bluebrain.nexus.sourcing.projections.Fixture.memoize
import ch.epfl.bluebrain.nexus.sourcing.projections.ProjectionProgress._
import ch.epfl.bluebrain.nexus.sourcing.projections.ProjectionsSpec.SomeEvent
import io.circe.generic.auto._
import org.scalatest.concurrent.Eventually
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import org.scalatest.{BeforeAndAfterAll, DoNotDiscover}

import scala.concurrent.duration._

//noinspection TypeAnnotation
@DoNotDiscover
class ProjectionsSpec
    extends TestKitBase
    with AnyWordSpecLike
    with Matchers
    with TestHelpers
    with IOValues
    with Eventually
    with BeforeAndAfterAll {

  implicit override lazy val system: ActorSystem      = SystemBuilder.persistence("ProjectionsSpec")
  implicit private val contextShift: ContextShift[IO] = IO.contextShift(system.dispatcher)

  override protected def afterAll(): Unit = {
    TestKit.shutdownActorSystem(system)
    super.afterAll()
  }

  "A Projection" should {
    val id            = genString()
    val persistenceId = s"/some/${genString()}"
    val projections   = memoize(Projections[IO, SomeEvent]).unsafeRunSync()
    val progress      = OffsetProgress(Offset.sequence(42), 42, 42, 0)

    "store progress" in {
      projections.ioValue.recordProgress(id, progress).ioValue
    }

    "retrieve stored progress" in {
      projections.ioValue.progress(id).ioValue shouldEqual progress
    }

    "retrieve NoProgress for unknown projections" in {
      projections.ioValue.progress(genString()).ioValue shouldEqual NoProgress
    }

    val firstOffset: Offset  = Offset.sequence(42)
    val secondOffset: Offset = Offset.sequence(98)
    val firstEvent           = SomeEvent(1L, "description")
    val secondEvent          = SomeEvent(2L, "description2")

    "store an event" in {
      projections.ioValue.recordFailure(id, persistenceId, 1L, firstOffset, firstEvent).ioValue
    }

    "store another event" in {
      projections.ioValue.recordFailure(id, persistenceId, 2L, secondOffset, secondEvent).ioValue
    }

    "retrieve stored events" in {
      val expected = Seq((firstEvent, firstOffset), (secondEvent, secondOffset))
      eventually {
        logOf(projections.ioValue.failures(id)) should contain theSameElementsInOrderAs expected
      }
    }

    "retrieve empty list of events for unknown failure log" in {
      eventually {
        logOf(projections.ioValue.failures(genString())) shouldBe empty
      }
    }

  }

  private def logOf(source: Source[(SomeEvent, Offset), _]): Vector[(SomeEvent, Offset)] = {
    val f = source.runFold(Vector.empty[(SomeEvent, Offset)])(_ :+ _)
    IO.fromFuture(IO(f)).ioValue
  }

  implicit override def patienceConfig: PatienceConfig =
    PatienceConfig(30.seconds, 50.milliseconds)
}

object ProjectionsSpec {
  final case class SomeEvent(rev: Long, description: String)
} 
Example 152
Source File: IOValues.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.sourcing.projections

import cats.effect.IO
import org.scalactic.source
import org.scalatest.matchers.should.Matchers._
import org.scalatest.concurrent.ScalaFutures

import scala.reflect.ClassTag

trait IOValues extends ScalaFutures {
  implicit final def ioValues[A](io: IO[A]): IOValuesSyntax[A] =
    new IOValuesSyntax(io)

  protected class IOValuesSyntax[A](io: IO[A]) {
    def failed[Ex <: Throwable: ClassTag](implicit config: PatienceConfig, pos: source.Position): Ex = {
      val Ex = implicitly[ClassTag[Ex]]
      io.redeemWith(
          {
            case Ex(ex) => IO.pure(ex)
            case other  =>
              IO(
                fail(
                  s"Wrong throwable type caught, expected: '${Ex.runtimeClass.getName}', actual: '${other.getClass.getName}'"
                )
              )
          },
          a => IO(fail(s"The IO did not fail as expected, but computed the value '$a'"))
        )
        .ioValue(config, pos)
    }

    def ioValue(implicit config: PatienceConfig, pos: source.Position): A =
      io.unsafeToFuture().futureValue(config, pos)
  }
}

object IOValues extends IOValues 
Example 153
Source File: Fixture.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.sourcing.projections

import akka.persistence.journal.{Tagged, WriteEventAdapter}
import cats.effect.{IO, Sync}
import cats.effect.concurrent.Ref

import scala.annotation.nowarn

object Fixture {

  sealed trait Event
  final case object Executed           extends Event
  final case object OtherExecuted      extends Event
  final case object AnotherExecuted    extends Event
  final case object YetAnotherExecuted extends Event
  final case object RetryExecuted      extends Event
  final case object IgnoreExecuted     extends Event
  final case object NotDiscarded       extends Event
  final case object Discarded          extends Event

  sealed trait EventTransform
  final case object ExecutedTransform           extends EventTransform
  final case object OtherExecutedTransform      extends EventTransform
  final case object AnotherExecutedTransform    extends EventTransform
  final case object YetAnotherExecutedTransform extends EventTransform
  final case object RetryExecutedTransform      extends EventTransform
  final case object IgnoreExecutedTransform     extends EventTransform
  final case object NotDiscardedTransform       extends EventTransform
  final case object DiscardedTransform          extends EventTransform

  sealed trait Cmd
  final case object Execute           extends Cmd
  final case object ExecuteOther      extends Cmd
  final case object ExecuteAnother    extends Cmd
  final case object ExecuteYetAnother extends Cmd
  final case object ExecuteRetry      extends Cmd
  final case object ExecuteIgnore     extends Cmd

  sealed trait State
  final case object Perpetual extends State

  sealed trait Rejection
  final case object Reject extends Rejection

  class TaggingAdapter extends WriteEventAdapter {
    override def manifest(event: Any): String = ""
    override def toJournal(event: Any): Any   =
      event match {
        case Executed           => Tagged(event, Set("executed"))
        case OtherExecuted      => Tagged(event, Set("other"))
        case AnotherExecuted    => Tagged(event, Set("another"))
        case YetAnotherExecuted => Tagged(event, Set("yetanother"))
        case RetryExecuted      => Tagged(event, Set("retry"))
        case IgnoreExecuted     => Tagged(event, Set("ignore"))
        case NotDiscarded       => Tagged(event, Set("discard"))
        case Discarded          => Tagged(event, Set("discard"))

      }
  }

  val initial: State                                             = Perpetual
  @nowarn("cat=unused")
  def next(state: State, event: Event): State                    = Perpetual
  @nowarn("cat=unused")
  def eval(state: State, cmd: Cmd): IO[Either[Rejection, Event]] =
    cmd match {
      case Execute           => IO.pure(Right(Executed))
      case ExecuteOther      => IO.pure(Right(OtherExecuted))
      case ExecuteAnother    => IO.pure(Right(AnotherExecuted))
      case ExecuteYetAnother => IO.pure(Right(YetAnotherExecuted))
      case ExecuteRetry      => IO.pure(Right(RetryExecuted))
      case ExecuteIgnore     => IO.pure(Right(IgnoreExecuted))

    }

  def memoize[F[_], A](fa: F[A])(implicit F: Sync[F]): F[F[A]] = {
    import cats.implicits._
    for {
      ref <- Ref[F].of(fa.attempt)
      _   <- ref.update(_.flatTap(a => ref.set(a.pure[F])))
    } yield ref.get.flatten.rethrow
  }
} 
Example 154
Source File: InMemoryStateMachineSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.sourcing

import cats.effect.{ContextShift, IO, Timer}
import ch.epfl.bluebrain.nexus.sourcing.Command.{Increment, IncrementAsync, Initialize}
import ch.epfl.bluebrain.nexus.sourcing.State.Current
import ch.epfl.bluebrain.nexus.sourcing.StateMachineFixture._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class InMemoryStateMachineSpec extends SourcingSpec {

  implicit val ctx: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit val timer: Timer[IO]      = IO.timer(ExecutionContext.global)

  "An InMemoryStateMachine" should {

    val cache = StateMachine.inMemory[IO, Int]("global", initialState, evaluate[IO]).unsafeRunSync()

    "return its name" in {
      cache.name shouldEqual "global"
    }

    "update its state when accepting commands" in {
      cache.evaluate(1, Increment(0, 2)).unsafeRunSync().rightValue shouldEqual Current(1, 2)
      cache.evaluate(1, IncrementAsync(1, 5, 200.millis)).unsafeRunSync().rightValue shouldEqual Current(2, 7)
      cache.currentState(1).unsafeRunSync() shouldEqual Current(2, 7)
    }

    "test without applying changes" in {
      cache.test(1, Initialize(0)).unsafeRunSync().leftValue
      cache.test(1, Initialize(2)).unsafeRunSync().rightValue shouldEqual Current(3, 0)
      cache.test(1, Initialize(2)).unsafeRunSync().rightValue shouldEqual Current(3, 0)
      cache.currentState(1).unsafeRunSync() shouldEqual Current(2, 7)
    }

    "not update its state if evaluation fails" in {
      cache.evaluate(1, Initialize(0)).unsafeRunSync().leftValue
      cache.currentState(1).unsafeRunSync() shouldEqual Current(2, 7)
    }

    "evaluate commands one at a time" in {
      cache.evaluate(1, Initialize(2)).unsafeRunSync().rightValue shouldEqual Current(3, 0)
      cache.currentState(1).unsafeRunSync() shouldEqual Current(3, 0)
      cache.evaluate(1, IncrementAsync(3, 2, 300.millis)).unsafeToFuture()
      cache.evaluate(1, IncrementAsync(4, 2, 20.millis)).unsafeRunSync().rightValue shouldEqual Current(5, 4)
      cache.currentState(1).unsafeRunSync() shouldEqual Current(5, 4)
    }

  }

} 
Example 155
Source File: EvaluationSyntaxSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.sourcing

import cats.effect.IO
import org.scalatest.concurrent.ScalaFutures
import ch.epfl.bluebrain.nexus.sourcing.syntax._
import org.scalatest.EitherValues

class EvaluationSyntaxSpec extends SourcingSpec with ScalaFutures with EitherValues {
  type State   = (Int)
  type Command = Int
  "An evaluation syntax" should {
    "transform a '(state, command) => state' evaluation into a '(state, command) => F(Right(state))'" in {
      val eval: (State, Command) => State = {
        case (st, cmd) => (st + cmd)
      }
      val evalEitherF                     = eval.toEitherF[IO]
      evalEitherF(2, 3).unsafeRunSync().rightValue shouldEqual 5
    }

    "transform a '(state, command) => F(state)' evaluation into a '(state, command) => F(Right(state))'" in {
      val err                                 = new RuntimeException("error")
      val eval: (State, Command) => IO[State] = {
        case (st, cmd) if st < 0 || cmd < 0 => IO.raiseError(err)
        case (st, cmd)                      => IO.pure(st + cmd)
      }
      val evalEitherF                         = eval.toEither
      evalEitherF(1, 2).unsafeRunSync().rightValue shouldEqual 3
      evalEitherF(-1, 3).unsafeToFuture().failed.futureValue shouldEqual err
      evalEitherF(1, -3).unsafeToFuture().failed.futureValue shouldEqual err
    }
  }
} 
Example 156
Source File: ArchiveCacheSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.kg.archives

import java.time.{Clock, Instant, ZoneId}

import cats.effect.{IO, Timer}
import ch.epfl.bluebrain.nexus.admin.client.types.Project
import ch.epfl.bluebrain.nexus.commons.test.ActorSystemFixture
import ch.epfl.bluebrain.nexus.commons.test.io.IOOptionValues
import ch.epfl.bluebrain.nexus.iam.types.Identity.Anonymous
import ch.epfl.bluebrain.nexus.kg.TestHelper
import ch.epfl.bluebrain.nexus.kg.archives.Archive.{File, Resource, ResourceDescription}
import ch.epfl.bluebrain.nexus.kg.resources.Id
import ch.epfl.bluebrain.nexus.kg.resources.syntax._
import ch.epfl.bluebrain.nexus.service.config.Settings
import org.scalatest.concurrent.Eventually
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

import scala.concurrent.duration._

class ArchiveCacheSpec
    extends ActorSystemFixture("ArchiveCacheSpec", true)
    with TestHelper
    with AnyWordSpecLike
    with Matchers
    with IOOptionValues
    with Eventually {

  implicit override def patienceConfig: PatienceConfig = PatienceConfig(10.second, 50.milliseconds)

  private val appConfig                 = Settings(system).serviceConfig
  implicit private val config           =
    appConfig.copy(kg =
      appConfig.kg.copy(archives = appConfig.kg.archives.copy(cacheInvalidateAfter = 500.millis, maxResources = 100))
    )
  implicit private val timer: Timer[IO] = IO.timer(system.dispatcher)
  implicit private val archivesCfg      = config.kg.archives

  private val cache: ArchiveCache[IO] = ArchiveCache[IO].unsafeToFuture().futureValue
  implicit private val clock          = Clock.fixed(Instant.EPOCH, ZoneId.systemDefault())
  private val instant                 = clock.instant()

  def randomProject() = {
    val instant = Instant.EPOCH
    // format: off
    Project(genIri, genString(), genString(), None, genIri, genIri, Map.empty, genUUID, genUUID, 1L, false, instant, genIri, instant, genIri)
    // format: on
  }

  "An archive cache" should {

    "write and read an Archive" in {
      val resId     = Id(randomProject().ref, genIri)
      val resource1 = Resource(genIri, randomProject(), None, None, originalSource = true, None)
      val file1     = File(genIri, randomProject(), None, None, None)
      val archive   = Archive(resId, instant, Anonymous, Set(resource1, file1))
      val _         = cache.put(archive).value.some
      cache.get(archive.resId).value.some shouldEqual archive
    }

    "read a non existing resource" in {
      val resId = Id(randomProject().ref, genIri)
      cache.get(resId).value.ioValue shouldEqual None
    }

    "read after timeout" in {
      val resId   = Id(randomProject().ref, genIri)
      val set     = Set[ResourceDescription](Resource(genIri, randomProject(), None, None, originalSource = true, None))
      val archive = Archive(resId, instant, Anonymous, set)
      val _       = cache.put(archive).value.some
      val time    = System.currentTimeMillis()
      cache.get(resId).value.some shouldEqual archive
      eventually {
        cache.get(resId).value.ioValue shouldEqual None
      }
      val diff    = System.currentTimeMillis() - time
      diff should be > config.kg.archives.cacheInvalidateAfter.toMillis
      diff should be < config.kg.archives.cacheInvalidateAfter.toMillis + 300
    }
  }
} 
Example 157
Source File: DiskStorageOperationsSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.kg.storage

import java.nio.file.Paths

import akka.http.scaladsl.model.{ContentTypes, Uri}
import cats.effect.IO
import ch.epfl.bluebrain.nexus.commons.test._
import ch.epfl.bluebrain.nexus.commons.test.io.IOEitherValues
import ch.epfl.bluebrain.nexus.kg.config.KgConfig._
import ch.epfl.bluebrain.nexus.kg.resources.file.File.FileDescription
import ch.epfl.bluebrain.nexus.kg.resources.Id
import ch.epfl.bluebrain.nexus.kg.resources.ProjectIdentifier.ProjectRef
import ch.epfl.bluebrain.nexus.kg.{KgError, TestHelper}
import ch.epfl.bluebrain.nexus.service.config.Settings
import ch.epfl.bluebrain.nexus.sourcing.RetryStrategyConfig
import org.mockito.IdiomaticMockito
import org.scalatest.{BeforeAndAfter, OptionValues}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

import scala.concurrent.duration._

class DiskStorageOperationsSpec
    extends ActorSystemFixture("DiskStorageOperationsSpec")
    with AnyWordSpecLike
    with Matchers
    with BeforeAndAfter
    with IdiomaticMockito
    with IOEitherValues
    with Resources
    with TestHelper
    with OptionValues {

  implicit private val appConfig = Settings(system).serviceConfig

  implicit private val sc: StorageConfig = appConfig.kg.storage.copy(
    DiskStorageConfig(Paths.get("/tmp"), "SHA-256", read, write, false, 1024L),
    RemoteDiskStorageConfig("http://example.com", "v1", None, "SHA-256", read, write, true, 1024L),
    S3StorageConfig("MD5", read, write, true, 1024L),
    "password",
    "salt",
    RetryStrategyConfig("linear", 300.millis, 5.minutes, 100, 1.second)
  )

  private val project  = ProjectRef(genUUID)
  private val storage  = Storage.DiskStorage.default(project)
  private val resId    = Id(storage.ref, genIri)
  private val fileDesc = FileDescription("my file.txt", ContentTypes.`text/plain(UTF-8)`)

  "DiskStorageOperations" should {

    "verify when the storage exists" in {
      val verify = new DiskStorageOperations.VerifyDiskStorage[IO](storage)
      verify.apply.accepted
    }

    "save and fetch files" in {
      val save   = new DiskStorageOperations.SaveDiskFile[IO](storage)
      val fetch  = new DiskStorageOperations.FetchDiskFile[IO]()
      val source = genSource

      val attr    = save.apply(resId, fileDesc, source).ioValue
      attr.bytes shouldEqual 16L
      attr.filename shouldEqual fileDesc.filename
      attr.mediaType shouldEqual fileDesc.mediaType.value
      attr.location shouldEqual Uri(s"file:///tmp/${mangle(project, attr.uuid, "my%20file.txt")}")
      attr.path shouldEqual attr.location.path.tail.tail.tail
      val fetched = fetch.apply(attr).ioValue

      consume(source) shouldEqual consume(fetched)
    }

    "not link files" in {
      val link = new DiskStorageOperations.LinkDiskFile[IO]()
      link.apply(resId, fileDesc, Uri.Path("/foo")).failed[KgError] shouldEqual KgError.UnsupportedOperation
    }
  }

} 
Example 158
Source File: RemoteDiskStorageOperationsSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.kg.storage

import akka.http.scaladsl.model.ContentTypes._
import akka.http.scaladsl.model.Uri
import cats.effect.IO
import ch.epfl.bluebrain.nexus.commons.test.io.IOEitherValues
import ch.epfl.bluebrain.nexus.commons.test.{ActorSystemFixture, Resources}
import ch.epfl.bluebrain.nexus.iam.auth.AccessToken
import ch.epfl.bluebrain.nexus.iam.client.types.AuthToken
import ch.epfl.bluebrain.nexus.iam.types.Permission
import ch.epfl.bluebrain.nexus.kg.TestHelper
import ch.epfl.bluebrain.nexus.kg.resources.file.File.{Digest, FileAttributes, FileDescription}
import ch.epfl.bluebrain.nexus.kg.resources.Id
import ch.epfl.bluebrain.nexus.kg.resources.ProjectIdentifier.ProjectRef
import ch.epfl.bluebrain.nexus.kg.storage.Storage.RemoteDiskStorage
import ch.epfl.bluebrain.nexus.storage.client.StorageClient
import ch.epfl.bluebrain.nexus.storage.client.types.FileAttributes.{Digest => StorageDigest}
import ch.epfl.bluebrain.nexus.storage.client.types.{FileAttributes => StorageFileAttributes}
import org.mockito.{IdiomaticMockito, Mockito}
import org.scalatest.BeforeAndAfter
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

class RemoteDiskStorageOperationsSpec
    extends ActorSystemFixture("RemoteDiskStorageOperationsSpec")
    with AnyWordSpecLike
    with Matchers
    with BeforeAndAfter
    with IdiomaticMockito
    with IOEitherValues
    with Resources
    with TestHelper {

  private val endpoint = "http://nexus.example.com/v1"

  // TODO: Remove when migrating ADMIN client
  implicit private def oldTokenConversion(implicit token: Option[AccessToken]): Option[AuthToken] =
    token.map(t => AuthToken(t.value))

  sealed trait Ctx {
    val cred                                = genString()
    implicit val token: Option[AccessToken] = Some(AccessToken(cred))
    val path                                = Uri.Path(s"${genString()}/${genString()}")
    // format: off
    val storage = RemoteDiskStorage(ProjectRef(genUUID), genIri, 1L, false, false, "SHA-256", endpoint, Some(cred), genString(), Permission.unsafe(genString()), Permission.unsafe(genString()), 1024L)
    val attributes = FileAttributes(s"$endpoint/${storage.folder}/$path", path, s"${genString()}.json", `application/json`, 12L, Digest("SHA-256", genString()))
    // format: on
  }

  private val client = mock[StorageClient[IO]]

  before {
    Mockito.reset(client)
  }

  "RemoteDiskStorageOperations" should {

    "verify when storage exists" in new Ctx {
      client.exists(storage.folder) shouldReturn IO(true)
      val verify = new RemoteDiskStorageOperations.Verify[IO](storage, client)
      verify.apply.accepted
    }

    "verify when storage does not exists" in new Ctx {
      client.exists(storage.folder) shouldReturn IO(false)
      val verify = new RemoteDiskStorageOperations.Verify[IO](storage, client)
      verify.apply
        .rejected[
          String
        ] shouldEqual s"Folder '${storage.folder}' does not exists on the endpoint '${storage.endpoint}'"
    }

    "fetch file" in new Ctx {
      val source       = genSource
      client.getFile(storage.folder, path) shouldReturn IO(source)
      val fetch        = new RemoteDiskStorageOperations.Fetch[IO](storage, client)
      val resultSource = fetch.apply(attributes).ioValue
      consume(resultSource) shouldEqual consume(source)
    }

    "link file" in new Ctx {
      val id               = Id(storage.ref, genIri)
      val sourcePath       = Uri.Path(s"${genString()}/${genString()}")
      val destRelativePath = Uri.Path(mangle(storage.ref, attributes.uuid, attributes.filename))
      client.moveFile(storage.folder, sourcePath, destRelativePath) shouldReturn
        IO(
          StorageFileAttributes(
            attributes.location,
            attributes.bytes,
            StorageDigest(attributes.digest.algorithm, attributes.digest.value),
            attributes.mediaType
          )
        )
      val link             = new RemoteDiskStorageOperations.Link[IO](storage, client)
      link
        .apply(id, FileDescription(attributes.uuid, attributes.filename, Some(attributes.mediaType)), sourcePath)
        .ioValue shouldEqual attributes.copy(path = destRelativePath)
    }
  }
} 
Example 159
Source File: IOValues.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.util

import cats.effect.IO
import org.scalactic.source
import org.scalatest.matchers.should.Matchers._
import org.scalatest.concurrent.ScalaFutures

import scala.reflect.ClassTag

trait IOValues extends ScalaFutures {
  implicit final def ioValues[A](io: IO[A]): IOValuesSyntax[A] =
    new IOValuesSyntax(io)

  protected class IOValuesSyntax[A](io: IO[A]) {
    def failed[Ex <: Throwable: ClassTag](implicit config: PatienceConfig, pos: source.Position): Ex = {
      val Ex = implicitly[ClassTag[Ex]]
      io.redeemWith(
          {
            case Ex(ex) => IO.pure(ex)
            case other  =>
              IO(
                fail(
                  s"Wrong throwable type caught, expected: '${Ex.runtimeClass.getName}', actual: '${other.getClass.getName}'"
                )
              )
          },
          a => IO(fail(s"The IO did not fail as expected, but computed the value '$a'"))
        )
        .ioValue(config, pos)
    }

    def ioValue(implicit config: PatienceConfig, pos: source.Position): A =
      io.unsafeToFuture().futureValue(config, pos)
  }
}

object IOValues extends IOValues 
Example 160
Source File: IOEitherValues.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.util

import cats.effect.IO
import org.scalactic.source
import org.scalatest.matchers.should.Matchers.fail

import scala.reflect.ClassTag

trait IOEitherValues extends IOValues with EitherValues {

  implicit final def ioEitherValues[E, A](io: IO[Either[E, A]]): IOEitherValuesSyntax[E, A] =
    new IOEitherValuesSyntax(io)

  protected class IOEitherValuesSyntax[E, A](io: IO[Either[E, A]]) {
    def accepted(implicit config: PatienceConfig, pos: source.Position): A =
      io.ioValue(config, pos).rightValue

    def rejected[EE <: E: ClassTag](implicit config: PatienceConfig, pos: source.Position): EE = {
      val EE = implicitly[ClassTag[EE]]
      io.ioValue(config, pos).leftValue match {
        case EE(value) => value
        case other     =>
          fail(
            s"Wrong throwable type caught, expected: '${EE.runtimeClass.getName}', actual: '${other.getClass.getName}'"
          )
      }
    }
  }
}

object IOEitherValues extends IOEitherValues 
Example 161
Source File: InfluxProjectionSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.influx

import java.nio.file.Files

import cats.effect.{Blocker, IO}
import ch.epfl.bluebrain.nexus.cli.Console
import ch.epfl.bluebrain.nexus.cli.clients.InfluxClient
import ch.epfl.bluebrain.nexus.cli.config.AppConfig
import ch.epfl.bluebrain.nexus.cli.modules.influx.InfluxProjection
import ch.epfl.bluebrain.nexus.cli.sse.Offset
import fs2.io

//noinspection SqlNoDataSourceInspection
class InfluxProjectionSpec extends AbstractInfluxSpec {

  "A InfluxProjection" should {
    val brainParcelationExpected = jsonContentOf("/templates/influxdb-results-brain-parcelation.json")
    val cellRecordExpected       = jsonContentOf("/templates/influxdb-results-cell-record.json")
    "project distribution sizes" in { (proj: InfluxProjection[IO], client: InfluxClient[IO]) =>
      for {
        _                <- proj.run
        brainQueryResult <- client.query("""SELECT * FROM "brainParcelation"""")
        cellQueryResult  <- client.query("""SELECT * FROM "cellRecord"""")
        _                 = brainQueryResult shouldEqual Right(brainParcelationExpected)
        _                 = cellQueryResult shouldEqual Right(cellRecordExpected)
      } yield ()
    }
    "save offset" in { (cfg: AppConfig, blocker: Blocker, proj: InfluxProjection[IO], console: Console[IO]) =>
      implicit val b: Blocker     = blocker
      implicit val c: Console[IO] = console
      for {
        _      <- proj.run
        exists <- io.file.exists[IO](blocker, cfg.influx.offsetFile)
        _       = exists shouldEqual true
        _       = println(s"Offset file content '${Files.readString(cfg.influx.offsetFile)}'")
        offset <- Offset.load(cfg.influx.offsetFile)
        _       = offset.nonEmpty shouldEqual true
      } yield ()
    }
  }
} 
Example 162
Source File: AbstractInfluxSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.influx

import cats.effect.{Blocker, IO, Resource}
import ch.epfl.bluebrain.nexus.cli.{AbstractCliSpec, Console}
import ch.epfl.bluebrain.nexus.cli.clients.InfluxClient
import ch.epfl.bluebrain.nexus.cli.config.AppConfig
import ch.epfl.bluebrain.nexus.cli.influx.InfluxDocker.InfluxHostConfig
import izumi.distage.model.definition.{Module, ModuleDef}
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.duration._

class AbstractInfluxSpec extends AbstractCliSpec {

  override protected def defaultModules: Module = {
    super.defaultModules ++ new InfluxDocker.Module[IO]
  }

  override def testModule: ModuleDef =
    new ModuleDef {
      make[AppConfig].fromEffect { host: InfluxHostConfig =>
        copyConfigs.flatMap {
          case (envFile, _, influxFile) =>
            AppConfig.load[IO](Some(envFile), influxConfigFile = Some(influxFile)).flatMap {
              case Left(value)  => IO.raiseError(value)
              case Right(value) =>
                val influxOffsetFile = influxFile.getParent.resolve("influx.offset")
                val cfg              = value.copy(influx =
                  value.influx.copy(
                    endpoint = host.endpoint,
                    offsetFile = influxOffsetFile,
                    offsetSaveInterval = 100.milliseconds
                  )
                )
                IO.pure(cfg)
            }
        }
      }
      make[InfluxClient[IO]].fromResource {
        (_: InfluxDocker.Container, cfg: AppConfig, blocker: Blocker, console: Console[IO]) =>
          BlazeClientBuilder[IO](blocker.blockingContext).resource.flatMap { client =>
            val influxClient = InfluxClient(client, cfg, console)
            waitForInfluxReady(influxClient).map(_ => influxClient)
          }
      }
    }

  private def waitForInfluxReady(
      client: InfluxClient[IO],
      maxDelay: FiniteDuration = 90.seconds
  ): Resource[IO, Unit] = {
    import retry.CatsEffect._
    import retry.RetryPolicies._
    import retry._
    val policy   = limitRetriesByCumulativeDelay[IO](maxDelay, constantDelay(5.second))
    val healthIO = retryingOnAllErrors(
      policy = policy,
      onError = (_: Throwable, _) => IO.delay(println("Influx Container not ready, retrying..."))
    ) {
      client.health.map {
        case Left(err) => throw err
        case Right(_)  => ()
      }
    }
    Resource.liftF(healthIO)
  }
} 
Example 163
Source File: CliSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli

import java.nio.file.{Files, Path}
import java.util.regex.Pattern.quote

import cats.effect.{ExitCode, IO}
import ch.epfl.bluebrain.nexus.cli.config.AppConfig
import ch.epfl.bluebrain.nexus.cli.dummies.TestConsole

class CliSpec extends AbstractCliSpec {

  "A CLI" should {
    "show the default config" in { (cli: Cli[IO], console: TestConsole[IO], cfg: AppConfig) =>
      for {
        code        <- cli.command(assemble("config show"))
        replacements = Map(
                         quote("{postgres-offset-file}") -> cfg.postgres.offsetFile.toString,
                         quote("{influx-offset-file}")   -> cfg.influx.offsetFile.toString
                       )
        expected     = contentOf("cli/config-show.txt", replacements)
        lines       <- console.stdQueue.dequeue1
        _            = lines.trim shouldEqual expected.trim
        _            = code shouldEqual ExitCode.Success
      } yield ()
    }
    "show the default help" in { (cli: Cli[IO], console: TestConsole[IO]) =>
      for {
        code    <- cli.command(assemble("--help"))
        expected = contentOf("cli/help-main.txt")
        lines   <- console.stdQueue.dequeue1
        _        = lines.trim shouldEqual expected.trim
        _        = code shouldEqual ExitCode.Success
      } yield ()
    }
  }

  override def copyConfigs: IO[(Path, Path, Path)] =
    IO {
      val parent       = Files.createTempDirectory(".nexus")
      val envFile      = parent.resolve("env.conf")
      val postgresFile = parent.resolve("postgres.conf")
      val influxFile   = parent.resolve("influx.conf")
      Files.copy(getClass.getClassLoader.getResourceAsStream("env.conf"), envFile)
      Files.copy(getClass.getClassLoader.getResourceAsStream("postgres-noprojects.conf"), postgresFile)
      Files.copy(getClass.getClassLoader.getResourceAsStream("influx-noprojects.conf"), influxFile)
      (envFile, postgresFile, influxFile)
    }

  def assemble(string: String): List[String] =
    string.split(" ").toList

} 
Example 164
Source File: AbstractPostgresSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.postgres

import cats.effect.IO
import ch.epfl.bluebrain.nexus.cli.AbstractCliSpec
import ch.epfl.bluebrain.nexus.cli.config.AppConfig
import ch.epfl.bluebrain.nexus.cli.postgres.PostgresDocker.PostgresHostConfig
import doobie.util.transactor.Transactor
import izumi.distage.model.definition.{Module, ModuleDef}

import scala.concurrent.duration._

class AbstractPostgresSpec extends AbstractCliSpec {

  override protected def defaultModules: Module = {
    super.defaultModules ++ new PostgresDocker.Module[IO]
  }

  override def testModule: ModuleDef =
    new ModuleDef {
      make[AppConfig].fromEffect { host: PostgresHostConfig =>
        copyConfigs.flatMap {
          case (envFile, postgresFile, _) =>
            AppConfig.load[IO](Some(envFile), Some(postgresFile)).flatMap {
              case Left(value)  => IO.raiseError(value)
              case Right(value) =>
                val postgresOffsetFile = postgresFile.getParent.resolve("postgres.offset")
                val cfg                = value.copy(postgres =
                  value.postgres.copy(
                    host = host.host,
                    port = host.port,
                    offsetFile = postgresOffsetFile,
                    offsetSaveInterval = 100.milliseconds
                  )
                )
                IO.pure(cfg)
            }
        }
      }
      make[Transactor[IO]].fromEffect { (_: PostgresDocker.Container, cfg: AppConfig) =>
        val xa = Transactor.fromDriverManager[IO](
          "org.postgresql.Driver",
          cfg.postgres.jdbcUrl,
          cfg.postgres.username,
          cfg.postgres.password
        )
        waitForPostgresReady(xa).as(xa)
      }
    }

  private def waitForPostgresReady(xa: Transactor[IO], maxDelay: FiniteDuration = 30.seconds): IO[Unit] = {
    import doobie.implicits._
    import retry.CatsEffect._
    import retry.RetryPolicies._
    import retry._
    val policy = limitRetriesByCumulativeDelay[IO](maxDelay, constantDelay(1.second))
    retryingOnAllErrors(
      policy = policy,
      onError = (_: Throwable, _) => IO.delay(println("Postgres Container not ready, retrying..."))
    ) {
      sql"select 1;".query[Int].unique.transact(xa)
    } *> IO.unit
  }
} 
Example 165
Source File: PostgresProjectionSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.postgres

import java.time.OffsetDateTime

import cats.effect.{Blocker, IO}
import ch.epfl.bluebrain.nexus.cli.Console
import ch.epfl.bluebrain.nexus.cli.config.AppConfig
import ch.epfl.bluebrain.nexus.cli.modules.postgres.PostgresProjection
import ch.epfl.bluebrain.nexus.cli.modules.postgres.PostgresProjection.TimeMeta.javatime._
import ch.epfl.bluebrain.nexus.cli.sse.Offset
import doobie.util.transactor.Transactor
import fs2.io

//noinspection SqlNoDataSourceInspection
class PostgresProjectionSpec extends AbstractPostgresSpec {

  "A PostgresProjection" should {
    "project all schemas" in {
      import doobie.implicits._
      (xa: Transactor[IO], proj: PostgresProjection[IO]) =>
        for {
          _                                <- proj.run
          count                            <- sql"select count(id) from schemas;".query[Int].unique.transact(xa)
          _                                 = count shouldEqual 175
          maxImport                        <- sql"select id, count(import) from schema_imports group by id order by count desc limit 1;"
                                                .query[(String, Int)]
                                                .unique
                                                .transact(xa)
          (maxImportSchema, maxImportCount) = maxImport
          _                                 = maxImportSchema shouldEqual "https://neuroshapes.org/commons/entity"
          _                                 = maxImportCount shouldEqual 7
          lastUpdated                      <- sql"select last_updated from schemas where id = 'https://neuroshapes.org/commons/entity'"
                                                .query[OffsetDateTime]
                                                .unique
                                                .transact(xa)
          _                                 = lastUpdated.toInstant.toEpochMilli shouldEqual 1584615316089L
        } yield ()
    }
    "save offset" in { (cfg: AppConfig, blocker: Blocker, proj: PostgresProjection[IO], console: Console[IO]) =>
      implicit val b: Blocker     = blocker
      implicit val c: Console[IO] = console

      for {
        _      <- proj.run
        exists <- io.file.exists[IO](blocker, cfg.postgres.offsetFile)
        _       = exists shouldEqual true
        offset <- Offset.load(cfg.postgres.offsetFile)
        _       = offset.nonEmpty shouldEqual true
      } yield ()
    }
  }
} 
Example 166
Source File: InfluxPointSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import java.time.Instant
import java.util.regex.Pattern.quote

import cats.effect.IO
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.config.influx.TypeConfig
import ch.epfl.bluebrain.nexus.cli.utils.{Resources, TimeTransformation}
import fs2._
import fs2.text._
import org.http4s.EntityEncoder
import org.scalatest.Inspectors
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

class InfluxPointSpec extends AnyWordSpecLike with Matchers with Resources with Inspectors with TimeTransformation {

  private def writeToString[A](a: A)(implicit W: EntityEncoder[IO, A]): String =
    Stream
      .emit(W.toEntity(a))
      .covary[IO]
      .flatMap(_.body)
      .through(utf8Decode)
      .foldMonoid
      .compile
      .last
      .map(_.getOrElse(""))
      .unsafeRunSync

  "An InfluxPoint" should {

    val created = Instant.now()
    val updated = created.plusSeconds(5)

    "be created from SparqlResults" in {

      val sparqlResults = jsonContentOf(
        "/templates/sparql-results-influx.json",
        Map(
          quote("{created}") -> created.toString,
          quote("{updated}") -> updated.toString,
          quote("{bytes}")   -> 1234.toString,
          quote("{project}") -> "myorg/myproject"
        )
      ).as[SparqlResults].getOrElse(throw new IllegalArgumentException)

      val typeConfig = TypeConfig("https://neuroshapes.org/Subject", "", "datastats", Set("bytes"), "updated")

      val expected = InfluxPoint(
        "datastats",
        Map("created" -> created.toString, "project" -> "myorg/myproject", "deprecated" -> "false"),
        Map("bytes"   -> "1234"),
        Some(updated)
      )

      InfluxPoint.fromSparqlResults(sparqlResults, typeConfig) shouldEqual
        List(expected)

    }

    "converted to string" in {
      val point      = InfluxPoint(
        "m1",
        Map("created" -> created.toString, "project" -> "org/proj", "deprecated" -> "false"),
        Map("bytes"   -> "1234"),
        Some(updated)
      )
      val pointNoTag = InfluxPoint(
        "m2",
        Map.empty,
        Map("bytes" -> "2345"),
        Some(updated)
      )

      val list = List(
        point      -> s"m1,created=${created.toString},project=org/proj,deprecated=false bytes=1234 ${toNano(updated)}",
        pointNoTag -> s"m2 bytes=2345 ${toNano(updated)}"
      )

      forAll(list) {
        case (point, str) => writeToString(point) shouldEqual str
      }
    }
  }

} 
Example 167
Source File: SparqlClientSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import cats.effect.IO
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.{AbstractCliSpec, Console}
import ch.epfl.bluebrain.nexus.cli.CliError.ClientError.{ClientStatusError, ServerStatusError}
import ch.epfl.bluebrain.nexus.cli.config.{AppConfig, EnvConfig}
import ch.epfl.bluebrain.nexus.cli.sse._
import ch.epfl.bluebrain.nexus.cli.utils.Http4sExtras
import izumi.distage.model.definition.ModuleDef
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.headers.`Content-Type`
import org.http4s.{HttpApp, Response, Status, Uri}
import org.scalatest.OptionValues

class SparqlClientSpec extends AbstractCliSpec with Http4sExtras with OptionValues {

  private val sparqlResultsJson = jsonContentOf("/templates/sparql-results.json")
  private val sparqlResults     = sparqlResultsJson.as[SparqlResults].toOption.value
  private val query             = "SELECT * {?s ?p ?o} LIMIT 10"

  override def overrides: ModuleDef =
    new ModuleDef {
      include(defaultModules)
      make[Client[IO]].from { cfg: AppConfig =>
        val token   = cfg.env.token
        val ct      = `Content-Type`(SparqlClient.`application/sparql-query`)
        val view    = cfg.env.defaultSparqlView.renderString
        val httpApp = HttpApp[IO] {
          // success
          case req @ POST -> `v1` / "views" / OrgLabelVar(`orgLabel`) / ProjectLabelVar(
                `projectLabel`
              ) / `view` / "sparql" contentType `ct` optbearer `token` =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.Ok).withEntity(sparqlResultsJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
          // unknown view id
          case req @ POST -> `v1` / "views" / OrgLabelVar(`orgLabel`) / ProjectLabelVar(
                `projectLabel`
              ) / (_: String) / "sparql" contentType `ct` optbearer `token` =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.NotFound).withEntity(notFoundJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
          // unknown token
          case req @ POST -> `v1` / "views" / OrgLabelVar(`orgLabel`) / ProjectLabelVar(
                `projectLabel`
              ) / `view` / "sparql" contentType `ct` optbearer (_: Option[
                BearerToken
              ]) =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.Forbidden).withEntity(authFailedJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
          // other - internal error
          case req @ POST -> "v1" /: (_: Path) contentType `ct` optbearer `token` =>
            req.as[String].flatMap {
              case `query` => Response[IO](Status.InternalServerError).withEntity(internalErrorJson).pure[IO]
              case _       => Response[IO](Status.BadRequest).pure[IO]
            }
        }
        Client.fromHttpApp(httpApp)
      }
    }

  "A SparqlClient" should {
    "return sparql results" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env, console)
      for {
        results <- cl.query(orgLabel, projectLabel, query)
        _        = results shouldEqual Right(sparqlResults)
      } yield ()
    }
    "return not found" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env, console)
      for {
        results <- cl.query(orgLabel, projectLabel, Uri.unsafeFromString(genString()), query)
        _        = results shouldEqual Left(ClientStatusError(Status.NotFound, notFoundJson.noSpaces))
      } yield ()
    }
    "return internal error" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env, console)
      for {
        results <- cl.query(orgLabel, ProjectLabel(genString()), Uri.unsafeFromString(genString()), query)
        _        = results shouldEqual Left(ServerStatusError(Status.InternalServerError, internalErrorJson.noSpaces))
      } yield ()
    }
    "return bad token" in { (client: Client[IO], console: Console[IO], env: EnvConfig) =>
      val cl = SparqlClient(client, env.copy(token = Some(BearerToken("bad"))), console)
      for {
        results <- cl.query(orgLabel, projectLabel, query)
        _        = results shouldEqual Left(ClientStatusError(Status.Forbidden, authFailedJson.noSpaces))
      } yield ()
    }

  }
} 
Example 168
Source File: ProjectClientSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.clients

import java.util.UUID

import cats.effect.IO
import cats.effect.concurrent.Ref
import cats.implicits._
import ch.epfl.bluebrain.nexus.cli.{AbstractCliSpec, Console}
import ch.epfl.bluebrain.nexus.cli.CliError.ClientError.ClientStatusError
import ch.epfl.bluebrain.nexus.cli.config.{AppConfig, EnvConfig}
import ch.epfl.bluebrain.nexus.cli.sse._
import ch.epfl.bluebrain.nexus.cli.utils.Http4sExtras
import izumi.distage.model.definition.ModuleDef
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.{HttpApp, Response, Status}

class ProjectClientSpec extends AbstractCliSpec with Http4sExtras {

  private val projectJson = jsonContentOf("/templates/project.json", replacements)

  type Cache    = Map[(OrgUuid, ProjectUuid), (OrgLabel, ProjectLabel)]
  type CacheRef = Ref[IO, Cache]

  override def overrides: ModuleDef =
    new ModuleDef {
      include(defaultModules)
      make[Client[IO]].from { cfg: AppConfig =>
        val token   = cfg.env.token
        val httpApp = HttpApp[IO] {
          case GET -> `v1` / "projects" / OrgUuidVar(`orgUuid`) / ProjectUuidVar(`projectUuid`) optbearer `token` =>
            Response[IO](Status.Ok).withEntity(projectJson).pure[IO]
          case GET -> `v1` / "projects" / OrgUuidVar(_) / ProjectUuidVar(_) optbearer `token`                     =>
            Response[IO](Status.NotFound).withEntity(notFoundJson).pure[IO]
          case GET -> `v1` / "projects" / OrgUuidVar(_) / ProjectUuidVar(_) bearer (_: BearerToken)               =>
            Response[IO](Status.Forbidden).withEntity(authFailedJson).pure[IO]
        }
        Client.fromHttpApp(httpApp)
      }
      make[CacheRef].fromEffect {
        Ref.of[IO, Cache](Map.empty)
      }
    }

  "A ProjectClient" should {
    "resolve a known (orgUuid, projUuid) pair" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val cl = ProjectClient[IO](client, env, cache, console)
        for {
          labels <- cl.labels(orgUuid, projectUuid)
          _       = labels shouldEqual Right((orgLabel, projectLabel))
        } yield ()
    }
    "resolve from cache a known (orgUuid, projUuid) pair" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val errClient = Client.fromHttpApp(HttpApp[IO] { case GET -> Root => IO.pure(Response[IO](Status.NotFound)) })
        for {
          _      <- ProjectClient[IO](client, env, cache, console).labels(orgUuid, projectUuid)
          labels <- ProjectClient[IO](errClient, env, cache, console).labels(orgUuid, projectUuid)
          _       = labels shouldEqual Right((orgLabel, projectLabel))
        } yield ()
    }
    "fail to resolve an unknown (orgUuid, projUuid) pair" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val cl = ProjectClient[IO](client, env, cache, console)
        for {
          labels <- cl.labels(OrgUuid(UUID.randomUUID()), projectUuid)
          _       = labels shouldEqual Left(ClientStatusError(Status.NotFound, notFoundJson.noSpaces))
        } yield ()
    }
    "fail to resolve a known (orgUuid, projUuid) pair with bad credentials" in {
      (client: Client[IO], console: Console[IO], cache: CacheRef, env: EnvConfig) =>
        val cl = ProjectClient[IO](client, env.copy(token = Some(BearerToken("bad"))), cache, console)
        for {
          labels <- cl.labels(orgUuid, projectUuid)
          _       = labels shouldEqual Left(ClientStatusError(Status.Forbidden, authFailedJson.noSpaces))
        } yield ()
    }
  }
} 
Example 169
Source File: OffsetSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli.sse

import java.nio.file.{Files, Path}
import java.util.UUID

import cats.effect.{Blocker, ContextShift, IO}
import ch.epfl.bluebrain.nexus.cli.Console
import ch.epfl.bluebrain.nexus.cli.dummies.TestConsole
import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

import scala.concurrent.ExecutionContext

class OffsetSpec extends AnyWordSpecLike with Matchers with OptionValues {

  implicit protected val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit private val blocker: Blocker       = Blocker.liftExecutionContext(ExecutionContext.global)
  implicit private val console: Console[IO]   = TestConsole[IO].unsafeRunSync()

  abstract class Ctx {
    protected val uuid: UUID = UUID.randomUUID
    protected val file: Path = Files.createTempFile("offset", ".conf")
  }

  "An offset" should {
    "be loaded from configuration" in new Ctx {
      Files.writeString(file, uuid.toString)
      (for {
        offset <- Offset.load(file)
        _       = offset.value shouldEqual Offset(uuid)
      } yield Files.deleteIfExists(file)).unsafeRunSync()
    }

    "be loaded from configuration but failed to convert to UUID" in new Ctx {
      Files.writeString(file, "not-an-uuid")
      (for {
        offset <- Offset.load(file)
        _       = offset shouldEqual None
      } yield Files.deleteIfExists(file)).unsafeRunSync()
    }

    "be written to file" in new Ctx {
      val offset = Offset(UUID.randomUUID())
      (for {
        _ <- offset.write(file)
        _  = Files.readString(file) shouldEqual offset.value.toString
      } yield Files.deleteIfExists(file)).unsafeRunSync()
    }
  }
} 
Example 170
Source File: ConsoleSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.cli

import cats.effect.IO
import ch.epfl.bluebrain.nexus.cli.dummies.TestConsole

class ConsoleSpec extends AbstractCliSpec {

  "A TestConsole" should {
    "record the println" in { (tc: TestConsole[IO], c: Console[IO]) =>
      for {
        _    <- c.println("line")
        line <- tc.stdQueue.dequeue1
        _     = line shouldEqual "line"
      } yield ()
    }
    "record the printlnErr" in { (tc: TestConsole[IO], c: Console[IO]) =>
      for {
        _    <- c.printlnErr("line")
        line <- tc.errQueue.dequeue1
        _     = line shouldEqual "line"
      } yield ()
    }
  }

} 
Example 171
Source File: AttributesCache.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.storage.attributes

import java.nio.file.Path
import java.time.Clock

import akka.actor.{ActorRef, ActorSystem}
import akka.pattern.{ask, AskTimeoutException}
import akka.util.Timeout
import cats.effect.{ContextShift, Effect, IO}
import cats.implicits._
import ch.epfl.bluebrain.nexus.storage.File.FileAttributes
import ch.epfl.bluebrain.nexus.storage.StorageError.{InternalError, OperationTimedOut}
import ch.epfl.bluebrain.nexus.storage.attributes.AttributesCacheActor.Protocol._
import ch.epfl.bluebrain.nexus.storage.config.AppConfig.DigestConfig
import com.typesafe.scalalogging.Logger

import scala.util.control.NonFatal

trait AttributesCache[F[_]] {

  
  def asyncComputePut(filePath: Path, algorithm: String): Unit
}

object AttributesCache {
  private[this] val logger = Logger[this.type]

  def apply[F[_], Source](implicit
      system: ActorSystem,
      clock: Clock,
      tm: Timeout,
      F: Effect[F],
      computation: AttributesComputation[F, Source],
      config: DigestConfig
  ): AttributesCache[F] =
    apply(system.actorOf(AttributesCacheActor.props(computation)))

  private[attributes] def apply[F[_]](
      underlying: ActorRef
  )(implicit system: ActorSystem, tm: Timeout, F: Effect[F]): AttributesCache[F] =
    new AttributesCache[F] {
      implicit private val contextShift: ContextShift[IO] = IO.contextShift(system.dispatcher)

      override def get(filePath: Path): F[FileAttributes] =
        IO.fromFuture(IO.shift(system.dispatcher) >> IO(underlying ? Get(filePath)))
          .to[F]
          .flatMap[FileAttributes] {
            case attributes: FileAttributes => F.pure(attributes)
            case other                      =>
              logger.error(s"Received unexpected reply from the file attributes cache: '$other'")
              F.raiseError(InternalError("Unexpected reply from the file attributes cache"))
          }
          .recoverWith {
            case _: AskTimeoutException =>
              F.raiseError(OperationTimedOut("reply from the file attributes cache timed out"))
            case NonFatal(th)           =>
              logger.error("Exception caught while exchanging messages with the file attributes cache", th)
              F.raiseError(InternalError("Exception caught while exchanging messages with the file attributes cache"))
          }

      override def asyncComputePut(filePath: Path, algorithm: String): Unit =
        underlying ! Compute(filePath)

    }
} 
Example 172
Source File: IamIdentitiesClient.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.storage

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.client.RequestBuilding.Get
import akka.http.scaladsl.model.HttpRequest
import akka.http.scaladsl.model.headers.OAuth2BearerToken
import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
import akka.util.ByteString
import cats.effect.{ContextShift, Effect, IO}
import cats.implicits._
import ch.epfl.bluebrain.nexus.rdf.implicits._
import ch.epfl.bluebrain.nexus.storage.IamIdentitiesClient.Identity._
import ch.epfl.bluebrain.nexus.storage.IamIdentitiesClient._
import ch.epfl.bluebrain.nexus.storage.IamIdentitiesClientError.IdentitiesSerializationError
import ch.epfl.bluebrain.nexus.storage.config.IamClientConfig
import de.heikoseeberger.akkahttpcirce.ErrorAccumulatingCirceSupport.{DecodingFailures => AccDecodingFailures}
import io.circe.Decoder.Result
import io.circe.{Decoder, DecodingFailure, HCursor}

import scala.concurrent.ExecutionContext

class IamIdentitiesClient[F[_]](config: IamClientConfig)(implicit F: Effect[F], as: ActorSystem)
    extends JsonLdCirceSupport {

  private val um: FromEntityUnmarshaller[Caller]      = unmarshaller[Caller]
  implicit private val ec: ExecutionContext           = as.dispatcher
  implicit private val contextShift: ContextShift[IO] = IO.contextShift(ec)

  def apply()(implicit credentials: Option[AccessToken]): F[Caller] =
    credentials match {
      case Some(token) => execute(Get(config.identitiesIri.asAkka).addCredentials(OAuth2BearerToken(token.value)))
      case None        => F.pure(Caller.anonymous)
    }

  private def execute(req: HttpRequest): F[Caller] = {
    IO.fromFuture(IO(Http().singleRequest(req))).to[F].flatMap { resp =>
      if (resp.status.isSuccess())
        IO.fromFuture(IO(um(resp.entity))).to[F].recoverWith {
          case err: AccDecodingFailures => F.raiseError(IdentitiesSerializationError(err.getMessage))
          case err: Error               => F.raiseError(IdentitiesSerializationError(err.getMessage))
        }
      else
        IO.fromFuture(IO(resp.entity.dataBytes.runFold(ByteString(""))(_ ++ _).map(_.utf8String)))
          .to[F]
          .flatMap { err => F.raiseError(IamIdentitiesClientError.unsafe(resp.status, err)) }
    }
  }

}

object IamIdentitiesClient {

  
    final case class Authenticated(realm: String) extends Identity

    private def decodeAnonymous(hc: HCursor): Result[Subject] =
      hc.get[String]("@type").flatMap {
        case "Anonymous" => Right(Anonymous)
        case _           => Left(DecodingFailure("Cannot decode Anonymous Identity", hc.history))
      }

    private def decodeUser(hc: HCursor): Result[Subject] =
      (hc.get[String]("subject"), hc.get[String]("realm")).mapN {
        case (subject, realm) => User(subject, realm)
      }

    private def decodeGroup(hc: HCursor): Result[Identity] =
      (hc.get[String]("group"), hc.get[String]("realm")).mapN {
        case (group, realm) => Group(group, realm)
      }

    private def decodeAuthenticated(hc: HCursor): Result[Identity] =
      hc.get[String]("realm").map(Authenticated)

    private val attempts =
      List[HCursor => Result[Identity]](decodeAnonymous, decodeUser, decodeGroup, decodeAuthenticated)

    implicit val identityDecoder: Decoder[Identity] =
      Decoder.instance { hc =>
        attempts.foldLeft(Left(DecodingFailure("Unexpected", hc.history)): Result[Identity]) {
          case (acc @ Right(_), _) => acc
          case (_, f)              => f(hc)
        }
      }
  }

} 
Example 173
Source File: IOValues.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.storage.utils

import cats.effect.IO
import org.scalactic.source
import org.scalatest.matchers.should.Matchers._
import org.scalatest.concurrent.ScalaFutures

import scala.reflect.ClassTag

trait IOValues extends ScalaFutures {
  implicit final def ioValues[A](io: IO[A]): IOValuesSyntax[A] =
    new IOValuesSyntax(io)

  protected class IOValuesSyntax[A](io: IO[A]) {
    def failed[Ex <: Throwable: ClassTag](implicit config: PatienceConfig, pos: source.Position): Ex = {
      val Ex = implicitly[ClassTag[Ex]]
      io.redeemWith(
          {
            case Ex(ex) => IO.pure(ex)
            case other  =>
              IO(
                fail(
                  s"Wrong throwable type caught, expected: '${Ex.runtimeClass.getName}', actual: '${other.getClass.getName}'"
                )
              )
          },
          a => IO(fail(s"The IO did not fail as expected, but computed the value '$a'"))
        )
        .ioValue(config, pos)
    }

    def ioValue(implicit config: PatienceConfig, pos: source.Position): A =
      io.unsafeToFuture().futureValue(config, pos)
  }
}

object IOValues extends IOValues 
Example 174
Source File: IOEitherValues.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.storage.utils

import cats.effect.IO
import org.scalactic.source
import org.scalatest.matchers.should.Matchers.fail

import scala.reflect.ClassTag

trait IOEitherValues extends IOValues with EitherValues {

  implicit final def ioEitherValues[E, A](io: IO[Either[E, A]]): IOEitherValuesSyntax[E, A] =
    new IOEitherValuesSyntax(io)

  protected class IOEitherValuesSyntax[E, A](io: IO[Either[E, A]]) {
    def accepted(implicit config: PatienceConfig, pos: source.Position): A =
      io.ioValue(config, pos).rightValue

    def rejected[EE <: E: ClassTag](implicit config: PatienceConfig, pos: source.Position): EE = {
      val EE = implicitly[ClassTag[EE]]
      io.ioValue(config, pos).leftValue match {
        case EE(value) => value
        case other     =>
          fail(
            s"Wrong throwable type caught, expected: '${EE.runtimeClass.getName}', actual: '${other.getClass.getName}'"
          )
      }
    }
  }
}

object IOEitherValues extends IOEitherValues 
Example 175
Source File: AttributesComputationSpec.scala    From nexus   with Apache License 2.0 5 votes vote down vote up
package ch.epfl.bluebrain.nexus.storage.attributes

import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths}

import akka.actor.ActorSystem
import akka.http.scaladsl.model.ContentTypes.`text/plain(UTF-8)`
import akka.testkit.TestKit
import cats.effect.IO
import ch.epfl.bluebrain.nexus.storage.File.{Digest, FileAttributes}
import ch.epfl.bluebrain.nexus.storage.StorageError.InternalError
import ch.epfl.bluebrain.nexus.storage.utils.IOValues
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

import scala.concurrent.ExecutionContextExecutor

class AttributesComputationSpec
    extends TestKit(ActorSystem("AttributesComputationSpec"))
    with AnyWordSpecLike
    with Matchers
    with IOValues {

  implicit private val ec: ExecutionContextExecutor = system.dispatcher

  private trait Ctx {
    val path           = Files.createTempFile("storage-test", ".txt")
    val (text, digest) = "something" -> "3fc9b689459d738f8c88a3a48aa9e33542016b7a4052e001aaa536fca74813cb"
  }

  "Attributes computation computation" should {
    val computation = AttributesComputation.akkaAttributes[IO]
    val alg         = "SHA-256"

    "succeed" in new Ctx {
      Files.write(path, text.getBytes(StandardCharsets.UTF_8))
      computation(path, alg).ioValue shouldEqual FileAttributes(
        s"file://$path",
        Files.size(path),
        Digest(alg, digest),
        `text/plain(UTF-8)`
      )
      Files.deleteIfExists(path)
    }

    "fail when algorithm is wrong" in new Ctx {
      Files.write(path, text.getBytes(StandardCharsets.UTF_8))
      computation(path, "wrong-alg").failed[InternalError]
    }

    "fail when file does not exists" in new Ctx {
      computation(Paths.get("/tmp/non/existing"), alg).failed[InternalError]
    }
  }
} 
Example 176
Source File: DoobieSpec.scala    From cron4s   with Apache License 2.0 5 votes vote down vote up
package cron4s
package doobie

import cats.effect.{IO, ContextShift}

import _root_.doobie._
import _root_.doobie.implicits._
import _root_.doobie.util.invariant._

import org.scalatest.matchers.should.Matchers
import org.scalatest.flatspec.AnyFlatSpec

import scala.concurrent.ExecutionContext

class DoobieSpec extends AnyFlatSpec with Matchers {
  implicit val contextShift: ContextShift[IO] =
    IO.contextShift(ExecutionContext.global)

  val xa = Transactor.fromDriverManager[IO](
    "org.h2.Driver",
    "jdbc:h2:mem:refined;DB_CLOSE_DELAY=-1",
    "sa",
    ""
  )

  def insertMeeting(meeting: Meeting) = {
    val createTable = sql"""
       create table meeting(
        meeting_id BIGINT AUTO_INCREMENT PRIMARY KEY,
        subject VARCHAR(255) NOT NULL,
        description VARCHAR(255) NOT NULL,
        frequency VARCHAR(255) NOT NULL
      )
      """

    val insertRecord = sql"""
      insert into meeting(subject, description, frequency)
      values(${meeting.subject}, ${meeting.description}, ${meeting.frequency})
      """

    for {
      _  <- createTable.update.run
      id <- insertRecord.update.withUniqueGeneratedKeys[Long]("meeting_id")
    } yield MeetingId(id)
  }

  def loadMeeting(meetingId: MeetingId) =
    sql"select subject, description, frequency from meeting where meeting_id = $meetingId"
      .query[Meeting]
      .unique

  "Doobie" should "store and retrieve a cron expression as a member of a storable data structure" in {
    val standUpMeeting = Meeting(
      "Daily stand-up",
      "Daily team morning stand-up meeting",
      Cron.unsafeParse("0 0 10 ? * mon-fri")
    )

    val tx = for {
      meetingId <- insertMeeting(standUpMeeting)
      loaded    <- loadMeeting(meetingId)
    } yield loaded

    val loadedMeeting = tx.transact(xa).unsafeRunSync()
    loadedMeeting shouldBe standUpMeeting
  }

  it should "throw a SecondaryValidationFailed in case the cron expression is invalid" in {
    assertThrows[SecondaryValidationFailed[CronExpr]] {
      sql"select '0- 0 30 * * ?'".query[CronExpr].unique.transact(xa).unsafeRunSync()
    }
  }
} 
Example 177
Source File: RegressionRun.scala    From Conseil   with Apache License 2.0 5 votes vote down vote up
package tech.cryptonomic.conseil.smoke.tests

import cats.effect.{ExitCode, IO, IOApp}
import tech.cryptonomic.conseil.smoke.tests.suites.RegressionSuite


  override def run(args: List[String]): IO[ExitCode] = {
    val defaultConfigFileName = "conseil-regression-tests.conf"

    val (conf, platform, network) = args match {
      case platform :: network :: configfile :: _ => (configfile, platform, Some(network))
      case platform :: configfile :: Nil => (configfile, platform, None)
      case platform :: Nil => (defaultConfigFileName, platform, None)
    }

    val configPrint = IO(
      println(
        s"Running with configuration from $conf and ${network.fold("no syncing to tezos")(net => "syncing to tezos " + net)}"
      )
    )

    for {
      _ <- configPrint
      probe <- RegressionSuite(conf, platform, network)
      _ <- probe.runRegressionSuite
    } yield ExitCode.Success

  }
} 
Example 178
Source File: Server.scala    From typedapi   with MIT License 5 votes vote down vote up
import typedapi.server._
import typedapi.server.http4s._
import cats.effect.IO
import org.http4s._
import org.http4s.circe._

object Server {

  implicit val decoder = jsonOf[IO, User]
  implicit val encoder = jsonEncoderOf[IO, User]

  val get: () => IO[Result[User]] = () => IO.pure(success(User("Joe", 42)))
  val put: () => IO[Result[User]] = get
  val post: () => IO[Result[User]] = get
  val delete: () => IO[Result[User]] = get

  val path: () => IO[Result[User]] = get

  val putBody: User => IO[Result[User]] = user => IO.pure(success(user))
  val segment: String => IO[Result[User]] = name => IO.pure(success(User(name, 42)))
  val search: String => IO[Result[User]] = segment

  val header: String => IO[Result[User]] = consumer => IO.pure(success(User("found: " + consumer, 42)))
  val fixed: () => IO[Result[User]] = get
  val client: () => IO[Result[User]] = get
  val coll: () => IO[Result[User]] = get
  val matching: Map[String, String] => IO[Result[User]] = matches => IO.pure(success(User(matches.mkString(","), 42)))

  val endpoints = deriveAll[IO](FromDefinition.MyApi).from(
    get, 
    put, 
    post, 
    delete, 
    path, 
    putBody, 
    segment, 
    search, 
    header, 
    fixed, 
    client, 
    coll, 
    matching
  )

  def main(args: Array[String]): Unit = {
    import org.http4s.server.blaze.BlazeBuilder

    val sm = ServerManager(BlazeBuilder[IO], "localhost", 9000)

    mount(sm, endpoints).unsafeRunSync()

    scala.io.StdIn.readLine("Press 'Enter' to stop ...")
  }
} 
Example 179
Source File: Client.scala    From typedapi   with MIT License 5 votes vote down vote up
import typedapi.client._
import typedapi.client.http4s._
import cats.effect.IO
import org.http4s._
import org.http4s.circe._

object Client {

  implicit val decoder = jsonOf[IO, User]
  implicit val encoder = jsonEncoderOf[IO, User]

  val (get, put, post, delete, path, putBody, segment, search, header, fixed, client, coll, matches) = deriveAll(FromDsl.MyApi)

  def main(args: Array[String]): Unit = {
    import User._
    import cats.effect.IO
    import org.http4s.client.blaze.Http1Client

    val cm = ClientManager(Http1Client[IO]().unsafeRunSync, "http://localhost", 9000)

    (for {
      u0 <- putBody(User("joe", 27)).run[IO](cm)
      u1 <- search("joe").run[IO](cm)
    } yield {
      println(u0)
      println(u1)
      ()
    }).unsafeRunSync()
  }
} 
Example 180
Source File: Http4sClientSupportSpec.scala    From typedapi   with MIT License 5 votes vote down vote up
package http.support.tests.client

import http.support.tests.{UserCoding, User, Api}
import typedapi.client._
import typedapi.client.http4s._
import cats.effect.IO
import org.http4s.client.blaze.Http1Client
import org.specs2.mutable.Specification

final class Http4sClientSupportSpec extends Specification {

  import UserCoding._

  sequential

  val cm = ClientManager(Http1Client[IO]().unsafeRunSync, "http://localhost", 9001)

  val server = TestServer.start()

  "http4s client support" >> {
    val (p, s, q, header, fixed, clInH, clFixH, clColl, serMatchH, serSendH, m0, m1, m2, m3, m4, m5, _, _, _) = deriveAll(Api)

    "paths and segments" >> {
      p().run[IO](cm).unsafeRunSync() === User("foo", 27)
      s("jim").run[IO](cm).unsafeRunSync() === User("jim", 27)
    }
    
    "queries" >> {
      q(42).run[IO](cm).unsafeRunSync() === User("foo", 42)
    }
    
    "headers" >> {
      header(42).run[IO](cm).unsafeRunSync() === User("foo", 42)
      fixed().run[IO](cm).unsafeRunSync() === User("joe", 27)
      clInH("jim").run[IO](cm).unsafeRunSync === User("jim", 27)
      clFixH().run[IO](cm).unsafeRunSync() === User("joe", 27)
      clColl(Map("coll" -> "joe", "collect" -> "jim")).run[IO](cm).unsafeRunSync === User("coll: joe,collect: jim", 27)
      serMatchH().run[IO](cm).unsafeRunSync() === User("joe", 27)
      serSendH().run[IO](cm).unsafeRunSync() === User("joe", 27)
    }

    "methods" >> {
      m0().run[IO](cm).unsafeRunSync() === User("foo", 27)
      m1().run[IO](cm).unsafeRunSync() === User("foo", 27)
      m2(User("jim", 42)).run[IO](cm).unsafeRunSync() === User("jim", 42)
      m3().run[IO](cm).unsafeRunSync() === User("foo", 27)
      m4(User("jim", 42)).run[IO](cm).unsafeRunSync() === User("jim", 42)
      m5(List("because")).run[IO](cm).unsafeRunSync() === User("foo", 27)
    }

    step {
      server.shutdown.unsafeRunSync()
    }
  }
} 
Example 181
Source File: Http4sServerSupportSpec.scala    From typedapi   with MIT License 5 votes vote down vote up
package http.support.tests.server

import http.support.tests.{UserCoding, Api}
import typedapi.server._
import typedapi.server.http4s._
import cats.effect.IO
import org.http4s.server.blaze.BlazeBuilder

final class Http4sServerSupportSpec extends ServerSupportSpec[IO] {

  import UserCoding._

  val endpoints = deriveAll[IO](Api).from(
    path, 
    segment, 
    query, 
    header, 
    fixed, 
    input, 
    clientHdr,
    coll,
    matching, 
    send, 
    get, 
    put, 
    putB, 
    post, 
    postB, 
    delete, 
    code200, 
    code400, 
    code500
  )
  val sm        = ServerManager(BlazeBuilder[IO], "localhost", 9000)
  val server    = mount(sm, endpoints).unsafeRunSync()

  "http4s implements TypedApi's server interface" >> {
    tests(9000)

    step {
      server.shutdown.unsafeRunSync()
    }
  }

} 
Example 182
Source File: TimeService.scala    From get-programming-with-scala   with MIT License 5 votes vote down vote up
package org.example.time

import java.time.format.DateTimeFormatter

import cats.effect.IO
import org.http4s.HttpService
import org.http4s.dsl.Http4sDsl

class TimeService extends Http4sDsl[IO] {

  private val printer = new TimePrinter(DateTimeFormatter.RFC_1123_DATE_TIME)

  val service = HttpService[IO] {
    case GET -> Root / "datetime" / country =>
      try {
        Ok(printer.now(country))
      } catch {
        case ex: IllegalStateException => NotFound(ex.getMessage)
      }
  }

} 
Example 183
Source File: EthereumPostgresSpout.scala    From Raphtory   with Apache License 2.0 5 votes vote down vote up
package com.raphtory.examples.blockchain.spouts

import cats.effect.Blocker
import cats.effect.IO
import com.raphtory.core.components.Spout.SpoutTrait
import doobie.implicits._
import doobie.util.ExecutionContexts
import doobie.util.transactor.Transactor

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.duration.MILLISECONDS
import scala.concurrent.duration.SECONDS

class EthereumPostgresSpout extends SpoutTrait {
  var startBlock = System.getenv().getOrDefault("STARTING_BLOCK", "46147").trim.toInt //first block to have a transaction by default
  val batchSize  = System.getenv().getOrDefault("BLOCK_BATCH_SIZE", "100").trim.toInt //number of blocks to pull each query
  val maxblock   = System.getenv().getOrDefault("MAX_BLOCK", "8828337").trim.toInt    //Maximum block in database to stop querying once this is reached

  val dbURL      = System.getenv().getOrDefault("DB_URL", "jdbc:postgresql:ether").trim //db connection string, default is for local with db called ether
  val dbUSER     = System.getenv().getOrDefault("DB_USER", "postgres").trim             //db user defaults to postgres
  val dbPASSWORD = System.getenv().getOrDefault("DB_PASSWORD", "").trim                 //default no password

  // querying done with doobie wrapper for JDBC (https://tpolecat.github.io/doobie/)
  implicit val cs = IO.contextShift(ExecutionContexts.synchronous)
  val dbconnector = Transactor.fromDriverManager[IO](
          "org.postgresql.Driver",
          dbURL,
          dbUSER,
          dbPASSWORD,
          Blocker.liftExecutionContext(ExecutionContexts.synchronous)
  )

  override def ProcessSpoutTask(message: Any): Unit = message match {
    case StartSpout  => AllocateSpoutTask(Duration(1, MILLISECONDS), "nextBatch")
    case "nextBatch" => running()
    case _           => println("message not recognized!")
  }

  protected def running(): Unit = {
    sql"select from_address, to_address, value,block_timestamp from transactions where block_number >= $startBlock AND block_number < ${startBlock + batchSize} "
      .query[
              (String, String, String, String)
      ]                                      //get the to,from,value and time for transactions within the set block batch
      .to[List]                              // ConnectionIO[List[String]]
      .transact(dbconnector)                 // IO[List[String]]
      .unsafeRunSync                         // List[String]
      .foreach(x => sendTuple(x.toString())) //send each transaction to the routers

    startBlock += batchSize                                   //increment batch for the next query
    if (startBlock > maxblock) stop()                         //if we have reached the max block we stop querying the database
    AllocateSpoutTask(Duration(1, MILLISECONDS), "nextBatch") // line up the next batch
  }
} 
Example 184
Source File: StaticLoggerBinder.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package org.slf4j.impl

import cats.effect.{ConcurrentEffect, ContextShift, IO, Timer}
import io.odin._
import io.odin.slf4j.{BufferingLogger, OdinLoggerBinder}

import scala.concurrent.ExecutionContext

class StaticLoggerBinder extends OdinLoggerBinder[IO] {

  val ec: ExecutionContext = scala.concurrent.ExecutionContext.global
  implicit val timer: Timer[IO] = IO.timer(ec)
  implicit val cs: ContextShift[IO] = IO.contextShift(ec)
  implicit val F: ConcurrentEffect[IO] = IO.ioConcurrentEffect

  val loggers: PartialFunction[String, Logger[IO]] = {
    case Level.Trace.toString => new BufferingLogger[IO](Level.Trace)
    case Level.Debug.toString => new BufferingLogger[IO](Level.Debug)
    case Level.Info.toString  => new BufferingLogger[IO](Level.Info)
    case Level.Warn.toString  => new BufferingLogger[IO](Level.Warn)
    case Level.Error.toString => new BufferingLogger[IO](Level.Error)
    case _ =>
      new BufferingLogger[IO](Level.Trace)
  }
}

object StaticLoggerBinder extends StaticLoggerBinder {

  var REQUESTED_API_VERSION: String = "1.7"

  def getSingleton: StaticLoggerBinder = this

} 
Example 185
Source File: ClassBasedRouting.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.examples

import cats.effect.{ExitCode, IO, IOApp}
import io.odin._
import io.odin.config._


object ClassBasedRouting extends IOApp {
  val logger: Logger[IO] =
    classRouting[IO](
      classOf[Foo[_]] -> consoleLogger[IO]().withMinimalLevel(Level.Warn),
      classOf[Bar[_]] -> consoleLogger[IO]().withMinimalLevel(Level.Info)
    ).withNoopFallback

  def run(args: List[String]): IO[ExitCode] = {
    (Foo(logger).log *> Bar(logger).log).as(ExitCode.Success)
  }
}

case class Foo[F[_]](logger: Logger[F]) {
  def log: F[Unit] = logger.info("foo")
}

case class Bar[F[_]](logger: Logger[F]) {
  def log: F[Unit] = logger.info("bar")
} 
Example 186
Source File: FilteringStackTrace.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.examples

import cats.effect.{ExitCode, IO, IOApp}
import io.odin._
import io.odin.formatter.Formatter
import io.odin.formatter.options.ThrowableFormat


object FilteringStackTrace extends IOApp {
  val throwableFormat: ThrowableFormat = ThrowableFormat(
    ThrowableFormat.Depth.Fixed(3),
    ThrowableFormat.Indent.NoIndent,
    ThrowableFormat.Filter.Excluding("cats.effect.IOApp", "cats.effect.internals.IOAppPlatform")
  )
  val logger: Logger[IO] = consoleLogger(formatter = Formatter.create(throwableFormat, colorful = true))

  def run(args: List[String]): IO[ExitCode] =
    logger.error("This is an exception", new RuntimeException("here")).as(ExitCode.Success)
} 
Example 187
Source File: EnclosureBasedRouting.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.examples

import cats.effect.{ExitCode, IO, IOApp}
import io.odin._
import io.odin.config._


object EnclosureBasedRouting extends IOApp {
  val logger: Logger[IO] =
    enclosureRouting(
      "io.odin.examples.EnclosureBasedRouting.foo" -> consoleLogger[IO]().withMinimalLevel(Level.Warn),
      "io.odin.examples.EnclosureBasedRouting.bar" -> consoleLogger[IO]().withMinimalLevel(Level.Info),
      "io.odin.examples" -> consoleLogger[IO]()
    ).withNoopFallback

  def zoo: IO[Unit] = logger.debug("Debug")
  def foo: IO[Unit] = logger.info("Never shown")
  def bar: IO[Unit] = logger.warn("Warning")

  def run(args: List[String]): IO[ExitCode] = {
    (zoo *> foo *> bar).as(ExitCode.Success)
  }
} 
Example 188
Source File: EqInstances.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin

import cats.Eq
import cats.effect.IO
import io.odin.formatter.Formatter
import org.scalacheck.Arbitrary

import scala.annotation.tailrec

trait EqInstances {
  @tailrec
  final def retrySample[T](implicit arb: Arbitrary[T]): T = arb.arbitrary.sample match {
    case Some(v) => v
    case _       => retrySample[T]
  }

  implicit def loggerEq[F[_]](
      implicit arbitraryString: Arbitrary[String],
      arbitraryCtx: Arbitrary[Map[String, String]],
      arbitraryThrowable: Arbitrary[Throwable],
      eqF: Eq[F[Unit]]
  ): Eq[Logger[F]] =
    (x: Logger[F], y: Logger[F]) => {
      val msg = retrySample[String]
      val ctx = retrySample[Map[String, String]]
      val throwable = retrySample[Throwable]
      eqF.eqv(x.trace(msg), y.trace(msg)) &&
      eqF.eqv(x.trace(msg, throwable), y.trace(msg, throwable)) &&
      eqF.eqv(x.trace(msg, ctx), y.trace(msg, ctx)) &&
      eqF.eqv(x.trace(msg, ctx, throwable), y.trace(msg, ctx, throwable)) &&
      eqF.eqv(x.debug(msg), y.debug(msg)) &&
      eqF.eqv(x.debug(msg, throwable), y.debug(msg, throwable)) &&
      eqF.eqv(x.debug(msg, ctx), y.debug(msg, ctx)) &&
      eqF.eqv(x.debug(msg, ctx, throwable), y.debug(msg, ctx, throwable)) &&
      eqF.eqv(x.info(msg), y.info(msg)) &&
      eqF.eqv(x.info(msg, throwable), y.info(msg, throwable)) &&
      eqF.eqv(x.info(msg, ctx), y.info(msg, ctx)) &&
      eqF.eqv(x.info(msg, ctx, throwable), y.info(msg, ctx, throwable)) &&
      eqF.eqv(x.warn(msg), y.warn(msg)) &&
      eqF.eqv(x.warn(msg, throwable), y.warn(msg, throwable)) &&
      eqF.eqv(x.warn(msg, ctx), y.warn(msg, ctx)) &&
      eqF.eqv(x.warn(msg, ctx, throwable), y.warn(msg, ctx, throwable)) &&
      eqF.eqv(x.error(msg), y.error(msg)) &&
      eqF.eqv(x.error(msg, throwable), y.error(msg, throwable)) &&
      eqF.eqv(x.error(msg, ctx), y.error(msg, ctx)) &&
      eqF.eqv(x.error(msg, ctx, throwable), y.error(msg, ctx, throwable))
    }

  implicit def eqIO[A](implicit eqA: Eq[A]): Eq[IO[A]] = Eq.instance { (ioA, ioB) =>
    eqA.eqv(ioA.unsafeRunSync(), ioB.unsafeRunSync())
  }

  implicit val loggerMessageEq: Eq[LoggerMessage] = Eq.instance { (lm1, lm2) =>
    val LoggerMessage(lvl1, msg1, context1, exception1, position1, threadName1, timestamp1) = lm1
    val LoggerMessage(lvl2, msg2, context2, exception2, position2, threadName2, timestamp2) = lm2
    lvl1 == lvl2 &&
    msg1.value == msg2.value &&
    context1 == context2 &&
    exception1 == exception2 &&
    position1 == position2 &&
    threadName1 == threadName2 &&
    timestamp1 == timestamp2
  }

  implicit def formatterEq(implicit arbLoggerMsg: Arbitrary[LoggerMessage]): Eq[Formatter] =
    Eq.instance { (fmt1, fmt2) =>
      val msg = retrySample[LoggerMessage]
      fmt1.format(msg) == fmt2.format(msg)
    }
} 
Example 189
Source File: ContextualLoggerSpec.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.loggers

import cats.arrow.FunctionK
import cats.data.{ReaderT, WriterT}
import cats.effect.{Clock, IO, Timer}
import cats.instances.list._
import cats.mtl.instances.all._
import io.odin.syntax._
import io.odin.{LoggerMessage, OdinSpec}

import scala.concurrent.duration.{FiniteDuration, TimeUnit}

class ContextualLoggerSpec extends OdinSpec {
  type W[A] = WriterT[IO, List[LoggerMessage], A]
  type F[A] = ReaderT[W, Map[String, String], A]

  implicit val hasContext: HasContext[Map[String, String]] = (env: Map[String, String]) => env
  implicit val timer: Timer[IO] = new Timer[IO] {
    def clock: Clock[IO] = new Clock[IO] {
      def realTime(unit: TimeUnit): IO[Long] = IO.pure(0)

      def monotonic(unit: TimeUnit): IO[Long] = IO.pure(0)
    }

    def sleep(duration: FiniteDuration): IO[Unit] = ???
  }

  private val logger = new WriterTLogger[IO].mapK(λ[FunctionK[W, F]](ReaderT.liftF(_))).withContext

  checkAll("ContContextLogger", LoggerTests[F](logger, reader => reader.run(Map()).written.unsafeRunSync()).all)

  it should "pick up context from F[_]" in {
    forAll { (loggerMessage: LoggerMessage, ctx: Map[String, String]) =>
      val List(written) = logger.log(loggerMessage).apply(ctx).written.unsafeRunSync()
      written.context shouldBe loggerMessage.context ++ ctx
    }
  }

  it should "embed context in all messages" in {
    forAll { (msgs: List[LoggerMessage], ctx: Map[String, String]) =>
      val written = logger.log(msgs).apply(ctx).written.unsafeRunSync()
      written.map(_.context) shouldBe msgs.map(_.context ++ ctx)
    }
  }
} 
Example 190
Source File: ConstContextLoggerSpec.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.loggers

import cats.data.WriterT
import cats.effect.{IO, Timer}
import cats.instances.list._
import io.odin.syntax._
import io.odin.{LoggerMessage, OdinSpec}

class ConstContextLoggerSpec extends OdinSpec {
  type F[A] = WriterT[IO, List[LoggerMessage], A]

  implicit val timer: Timer[IO] = zeroTimer

  checkAll(
    "ContextualLogger",
    LoggerTests[F](
      new WriterTLogger[IO].withConstContext(Map.empty),
      _.written.unsafeRunSync()
    ).all
  )

  it should "add constant context to the record" in {
    forAll { (loggerMessage: LoggerMessage, ctx: Map[String, String]) =>
      val logger = new WriterTLogger[IO].withConstContext(ctx)
      val List(written) = logger.log(loggerMessage).written.unsafeRunSync()
      written.context shouldBe loggerMessage.context ++ ctx
    }
  }

  it should "add constant context to the records" in {
    forAll { (messages: List[LoggerMessage], ctx: Map[String, String]) =>
      val logger = new WriterTLogger[IO].withConstContext(ctx)
      val written = logger.log(messages).written.unsafeRunSync()
      written.map(_.context) shouldBe messages.map(_.context ++ ctx)
    }
  }
} 
Example 191
Source File: FilterLoggerSpec.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.loggers

import cats.data.WriterT
import cats.effect.{IO, Timer}
import io.odin._
import io.odin.syntax._
import cats.instances.list._
import cats.syntax.all._

class FilterLoggerSpec extends OdinSpec {
  type F[A] = WriterT[IO, List[LoggerMessage], A]
  implicit val timer: Timer[IO] = zeroTimer

  checkAll(
    "FilterLogger",
    LoggerTests[F](new WriterTLogger[IO].filter(_.exception.isDefined), _.written.unsafeRunSync()).all
  )

  it should "logger.filter(p).log(msg) <-> F.whenA(p)(log(msg))" in {
    forAll { (msgs: List[LoggerMessage], p: LoggerMessage => Boolean) =>
      {
        val logger = new WriterTLogger[IO].filter(p)
        val written = msgs.traverse(logger.log).written.unsafeRunSync()
        val batchWritten = logger.log(msgs).written.unsafeRunSync()

        written shouldBe msgs.filter(p)
        batchWritten shouldBe written
      }
    }
  }
} 
Example 192
Source File: ConsoleLoggerSpec.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.loggers

import java.io.{ByteArrayOutputStream, PrintStream}

import cats.effect.{IO, Timer}
import cats.syntax.all._
import io.odin.Level._
import io.odin.formatter.Formatter
import io.odin.{Level, LoggerMessage, OdinSpec}

class ConsoleLoggerSpec extends OdinSpec {
  implicit val timer: Timer[IO] = IO.timer(scala.concurrent.ExecutionContext.global)

  it should "route all messages with level <= INFO to stdout" in {
    forAll { (loggerMessage: LoggerMessage, formatter: Formatter) =>
      whenever(loggerMessage.level <= Info) {
        val outBaos = new ByteArrayOutputStream()
        val stdOut = new PrintStream(outBaos)
        val errBaos = new ByteArrayOutputStream()
        val stdErr = new PrintStream(errBaos)

        val consoleLogger = ConsoleLogger[IO](formatter, stdOut, stdErr, Level.Trace)
        consoleLogger.log(loggerMessage).unsafeRunSync()
        outBaos.toString() shouldBe (formatter.format(loggerMessage) + System.lineSeparator())
      }
    }
  }

  it should "route all messages with level >= WARN to stderr" in {
    forAll { (loggerMessage: LoggerMessage, formatter: Formatter) =>
      whenever(loggerMessage.level > Info) {
        val outBaos = new ByteArrayOutputStream()
        val stdOut = new PrintStream(outBaos)
        val errBaos = new ByteArrayOutputStream()
        val stdErr = new PrintStream(errBaos)

        val consoleLogger = ConsoleLogger[IO](formatter, stdOut, stdErr, Level.Trace)
        consoleLogger.log(loggerMessage).unsafeRunSync()
        errBaos.toString() shouldBe (formatter.format(loggerMessage) + System.lineSeparator())
      }
    }
  }
} 
Example 193
Source File: LoggerNatTransformSpec.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.loggers

import cats.data.{Writer, WriterT}
import cats.effect.{Clock, IO, Timer}
import cats.{~>, Id}
import io.odin.{Level, Logger, LoggerMessage, OdinSpec}

import scala.concurrent.duration.{FiniteDuration, TimeUnit}

class LoggerNatTransformSpec extends OdinSpec {
  type F[A] = Writer[List[LoggerMessage], A]
  type FF[A] = WriterT[IO, List[LoggerMessage], A]

  it should "transform each method" in {
    forAll { (msg: String, ctx: Map[String, String], throwable: Throwable, timestamp: Long) =>
      implicit val clk: Timer[Id] = clock(timestamp)
      val logF = logger.withMinimalLevel(Level.Trace)
      val logFF = logF.mapK(nat).withMinimalLevel(Level.Trace)
      check(logF.trace(msg), logFF.trace(msg))
      check(logF.trace(msg, throwable), logFF.trace(msg, throwable))
      check(logF.trace(msg, ctx), logFF.trace(msg, ctx))
      check(logF.trace(msg, ctx, throwable), logFF.trace(msg, ctx, throwable))

      check(logF.debug(msg), logFF.debug(msg))
      check(logF.debug(msg, throwable), logFF.debug(msg, throwable))
      check(logF.debug(msg, ctx), logFF.debug(msg, ctx))
      check(logF.debug(msg, ctx, throwable), logFF.debug(msg, ctx, throwable))

      check(logF.info(msg), logFF.info(msg))
      check(logF.info(msg, throwable), logFF.info(msg, throwable))
      check(logF.info(msg, ctx), logFF.info(msg, ctx))
      check(logF.info(msg, ctx, throwable), logFF.info(msg, ctx, throwable))

      check(logF.warn(msg), logFF.warn(msg))
      check(logF.warn(msg, throwable), logFF.warn(msg, throwable))
      check(logF.warn(msg, ctx), logFF.warn(msg, ctx))
      check(logF.warn(msg, ctx, throwable), logFF.warn(msg, ctx, throwable))

      check(logF.error(msg), logFF.error(msg))
      check(logF.error(msg, throwable), logFF.error(msg, throwable))
      check(logF.error(msg, ctx), logFF.error(msg, ctx))
      check(logF.error(msg, ctx, throwable), logFF.error(msg, ctx, throwable))
    }
  }

  private val nat: F ~> FF = new (F ~> FF) {
    private val idToIo = new (Id ~> IO) {
      def apply[A](fa: Id[A]): IO[A] = IO.pure(fa)
    }

    def apply[A](fa: F[A]): FF[A] =
      fa.mapK(idToIo)
  }

  private def clock(timestamp: Long): Timer[Id] = new Timer[Id] {
    def clock: Clock[Id] = new Clock[Id] {
      def realTime(unit: TimeUnit): Id[Long] = timestamp
      def monotonic(unit: TimeUnit): Id[Long] = timestamp
    }

    def sleep(duration: FiniteDuration): Id[Unit] = ???
  }

  private def logger(implicit timer: Timer[Id]): Logger[F] = new WriterTLogger[Id]

  private def check(fnF: => F[Unit], fnFF: => FF[Unit]) = {
    val List(loggerMessageF) = fnF.written
    val List(loggerMessageFF) = fnFF.written.unsafeRunSync()
    loggerMessageEq.eqv(loggerMessageF, loggerMessageFF) shouldBe true
  }
} 
Example 194
Source File: LoggerMonoidSpec.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.loggers

import java.util.UUID

import cats.data.WriterT
import cats.effect.{Clock, IO, Timer}
import cats.instances.list._
import cats.instances.tuple._
import cats.instances.unit._
import cats.instances.uuid._
import cats.kernel.laws.discipline.MonoidTests
import cats.syntax.all._
import io.odin.{Level, Logger, LoggerMessage, OdinSpec}
import org.scalacheck.{Arbitrary, Gen}

import scala.concurrent.duration.{FiniteDuration, TimeUnit}

class LoggerMonoidSpec extends OdinSpec {
  type F[A] = WriterT[IO, List[(UUID, LoggerMessage)], A]

  checkAll("Logger", MonoidTests[Logger[F]].monoid)

  it should "(logger1 |+| logger2).log <-> (logger1.log |+| logger2.log)" in {
    forAll { (uuid1: UUID, uuid2: UUID, msg: LoggerMessage) =>
      val logger1: Logger[F] = NamedLogger(uuid1)
      val logger2: Logger[F] = NamedLogger(uuid2)
      val a = (logger1 |+| logger2).log(msg)
      val b = logger1.log(msg) |+| logger2.log(msg)
      a.written.unsafeRunSync() shouldBe b.written.unsafeRunSync()
    }
  }

  it should "(logger1 |+| logger2).log(list) <-> (logger1.log |+| logger2.log(list))" in {
    forAll { (uuid1: UUID, uuid2: UUID, msg: List[LoggerMessage]) =>
      val logger1: Logger[F] = NamedLogger(uuid1)
      val logger2: Logger[F] = NamedLogger(uuid2)
      val a = (logger1 |+| logger2).log(msg)
      val b = logger1.log(msg) |+| logger2.log(msg)
      a.written.unsafeRunSync() shouldBe b.written.unsafeRunSync()
    }
  }

  it should "set minimal level for underlying loggers" in {
    forAll { (uuid1: UUID, uuid2: UUID, level: Level, msg: List[LoggerMessage]) =>
      val logger1: Logger[F] = NamedLogger(uuid1)
      val logger2: Logger[F] = NamedLogger(uuid2)
      val a = (logger1 |+| logger2).withMinimalLevel(level).log(msg)
      val b = (logger1.withMinimalLevel(level) |+| logger2.withMinimalLevel(level)).log(msg)
      a.written.unsafeRunSync() shouldBe b.written.unsafeRunSync()
    }
  }

  case class NamedLogger(loggerId: UUID) extends DefaultLogger[F] {
    def log(msg: LoggerMessage): F[Unit] = WriterT.tell(List(loggerId -> msg))
  }

  implicit def timer: Timer[IO] = new Timer[IO] {
    def clock: Clock[IO] = new Clock[IO] {
      def realTime(unit: TimeUnit): IO[Long] = IO.pure(0)

      def monotonic(unit: TimeUnit): IO[Long] = IO.pure(0)
    }

    def sleep(duration: FiniteDuration): IO[Unit] = ???
  }

  implicit def arbitraryWriterLogger: Arbitrary[Logger[F]] = Arbitrary(
    Gen.uuid.map(NamedLogger)
  )
} 
Example 195
Source File: ContramapLoggerSpec.scala    From odin   with Apache License 2.0 5 votes vote down vote up
package io.odin.loggers

import cats.data.WriterT
import cats.effect.{IO, Timer}
import cats.instances.list._
import cats.syntax.all._
import io.odin.{LoggerMessage, OdinSpec}
import io.odin.syntax._

class ContramapLoggerSpec extends OdinSpec {
  type F[A] = WriterT[IO, List[LoggerMessage], A]
  implicit val timer: Timer[IO] = zeroTimer

  checkAll("ContramapLogger", LoggerTests[F](new WriterTLogger[IO].contramap(identity), _.written.unsafeRunSync()).all)

  it should "contramap(identity).log(msg) <-> log(msg)" in {
    val logger = new WriterTLogger[IO].contramap(identity)
    forAll { msgs: List[LoggerMessage] =>
      val written = msgs.traverse(logger.log).written.unsafeRunSync()
      val batchWritten = logger.log(msgs).written.unsafeRunSync()

      written shouldBe msgs
      batchWritten shouldBe written
    }
  }

  it should "contramap(f).log(msg) <-> log(f(msg))" in {
    forAll { (msgs: List[LoggerMessage], fn: LoggerMessage => LoggerMessage) =>
      val logger = new WriterTLogger[IO].contramap(fn)
      val written = msgs.traverse(logger.log).written.unsafeRunSync()
      val batchWritten = logger.log(msgs).written.unsafeRunSync()

      written shouldBe msgs.map(fn)
      batchWritten shouldBe written
    }
  }
} 
Example 196
Source File: S3CatsIOClientSupoprt.scala    From reactive-aws-clients   with MIT License 5 votes vote down vote up
package com.github.j5ik2o.reactive.aws.s3.cats

import java.io.File
import java.nio.file.Path

import cats.effect.IO
import software.amazon.awssdk.core.ResponseBytes
import software.amazon.awssdk.core.async.{ AsyncRequestBody, AsyncResponseTransformer }
import software.amazon.awssdk.services.s3.model._

trait S3CatsIOClientSupoprt { this: S3CatsIOClient =>
  override type RT[A, B] = AsyncResponseTransformer[A, B]
  override type RB       = AsyncRequestBody

  override def listBuckets(): IO[ListBucketsResponse] =
    IO.fromFuture {
      IO(underlying.listBuckets())
    }

  override def getObjectAsBytes(
      getObjectRequest: GetObjectRequest
  ): IO[ResponseBytes[GetObjectResponse]] = IO.fromFuture {
    IO(underlying.getObjectAsBytes(getObjectRequest))
  }

  override def getObjectToFile(getObjectRequest: GetObjectRequest, file: File): IO[GetObjectResponse] = IO.fromFuture {
    IO(underlying.getObjectToFile(getObjectRequest, file))
  }

  override def getObjectToPath(getObjectRequest: GetObjectRequest, destinationPath: Path): IO[GetObjectResponse] =
    IO.fromFuture {
      IO(underlying.getObjectToPath(getObjectRequest, destinationPath))
    }

  override def getObject[A](
      getObjectRequest: GetObjectRequest,
      responseTransformer: AsyncResponseTransformer[GetObjectResponse, A]
  ): IO[A] =
    IO.fromFuture {
      IO(underlying.getObject(getObjectRequest, responseTransformer))
    }

  override def getObjectTorrentAsBytes(
      getObjectRequest: GetObjectTorrentRequest
  ): IO[ResponseBytes[GetObjectTorrentResponse]] = IO.fromFuture {
    IO(underlying.getObjectTorrentAsBytes(getObjectRequest))
  }

  override def getObjectTorrentToFile(
      getObjectRequest: GetObjectTorrentRequest,
      file: File
  ): IO[GetObjectTorrentResponse] = IO.fromFuture {
    IO(underlying.getObjectTorrentToFile(getObjectRequest, file))
  }

  override def getObjectTorrentToPath(
      getObjectTorrentRequest: GetObjectTorrentRequest,
      destinationPath: Path
  ): IO[GetObjectTorrentResponse] = IO.fromFuture {
    IO(underlying.getObjectTorrentToPath(getObjectTorrentRequest, destinationPath))
  }

  override def getObjectTorrent[A](
      getObjectTorrentRequest: GetObjectTorrentRequest,
      responseTransformer: AsyncResponseTransformer[GetObjectTorrentResponse, A]
  ): IO[A] =
    IO.fromFuture {
      IO(underlying.getObjectTorrent(getObjectTorrentRequest, responseTransformer))
    }

  override def putObject(putObjectRequest: PutObjectRequest, requestBody: AsyncRequestBody): IO[PutObjectResponse] =
    IO.fromFuture {
      IO(underlying.putObject(putObjectRequest, requestBody))
    }

  override def putObjectFromPath(putObjectRequest: PutObjectRequest, sourcePath: Path): IO[PutObjectResponse] =
    IO.fromFuture {
      IO(underlying.putObjectFromPath(putObjectRequest, sourcePath))
    }

  override def putObjectFromFile(putObjectRequest: PutObjectRequest, sourceFile: File): IO[PutObjectResponse] =
    IO.fromFuture {
      IO(underlying.putObjectFromFile(putObjectRequest, sourceFile))
    }

  override def uploadPart(uploadPartRequest: UploadPartRequest, requestBody: AsyncRequestBody): IO[UploadPartResponse] =
    IO.fromFuture {
      IO(underlying.uploadPart(uploadPartRequest, requestBody))
    }

  override def uploadPartFromPath(uploadPartRequest: UploadPartRequest, sourcePath: Path): IO[UploadPartResponse] =
    IO.fromFuture {
      IO(underlying.uploadPartFromPath(uploadPartRequest, sourcePath))
    }

  override def uploadPartFromFile(uploadPartRequest: UploadPartRequest, sourceFile: File): IO[UploadPartResponse] =
    IO.fromFuture {
      IO(underlying.uploadPartFromFile(uploadPartRequest, sourceFile))
    }
} 
Example 197
Source File: DynamoDbStreamsCatsIOClient.scala    From reactive-aws-clients   with MIT License 5 votes vote down vote up
// Auto-Generated
package com.github.j5ik2o.reactive.aws.dynamodb.streams.cats

import cats.effect.{ ContextShift, IO }
import com.github.j5ik2o.reactive.aws.dynamodb.streams.{ DynamoDbStreamsAsyncClient, DynamoDbStreamsClient }
import software.amazon.awssdk.services.dynamodb.model._
import software.amazon.awssdk.services.dynamodb.streams.paginators.{ DescribeStreamPublisher, ListStreamsPublisher }

import scala.concurrent.{ ExecutionContext, Future }

object DynamoDbStreamsCatsIOClient {

  def apply(asyncClient: DynamoDbStreamsAsyncClient)(implicit ec: ExecutionContext): DynamoDbStreamsCatsIOClient =
    new DynamoDbStreamsCatsIOClient {
      override val executionContext: ExecutionContext     = ec
      override val underlying: DynamoDbStreamsAsyncClient = asyncClient
    }

}

trait DynamoDbStreamsCatsIOClient extends DynamoDbStreamsClient[IO] {

  val underlying: DynamoDbStreamsAsyncClient

  def executionContext: ExecutionContext
  implicit def cs: ContextShift[IO] = IO.contextShift(executionContext)

  override def describeStream(describeStreamRequest: DescribeStreamRequest): IO[DescribeStreamResponse] =
    IO.fromFuture {
      IO(underlying.describeStream(describeStreamRequest))
    }

  def describeStreamPaginator(describeStreamRequest: DescribeStreamRequest): DescribeStreamPublisher =
    underlying.describeStreamPaginator(describeStreamRequest)

  override def getRecords(getRecordsRequest: GetRecordsRequest): IO[GetRecordsResponse] =
    IO.fromFuture {
      IO(underlying.getRecords(getRecordsRequest))
    }

  override def getShardIterator(getShardIteratorRequest: GetShardIteratorRequest): IO[GetShardIteratorResponse] =
    IO.fromFuture {
      IO(underlying.getShardIterator(getShardIteratorRequest))
    }

  override def listStreams(listStreamsRequest: ListStreamsRequest): IO[ListStreamsResponse] =
    IO.fromFuture {
      IO(underlying.listStreams(listStreamsRequest))
    }

  override def listStreams(): IO[ListStreamsResponse] =
    IO.fromFuture {
      IO(underlying.listStreams())
    }

  def listStreamsPaginator(): ListStreamsPublisher =
    underlying.listStreamsPaginator()

  def listStreamsPaginator(listStreamsRequest: ListStreamsRequest): ListStreamsPublisher =
    underlying.listStreamsPaginator(listStreamsRequest)

} 
Example 198
Source File: PropPath.scala    From shaclex   with MIT License 5 votes vote down vote up
package es.weso.slang
import es.weso.rdf.nodes._
import es.weso.rdf._
import cats.effect.IO

trait PropPath extends Product with Serializable {
/*  private def showSet[A](vs: Set[A]): String = vs.size match {
    case 0 => s"{}"
    case 1 => vs.head.toString
    case _ => s"${vs.map(_.toString).mkString(",")}"
  } */

  def reach(n1: RDFNode, n2: RDFNode, rdf: RDFReader): IO[Boolean]
}

case class Pred(p: IRI) extends PropPath {
    override def reach(n1: RDFNode, 
                       n2: RDFNode, rdf: RDFReader
                       ): IO[Boolean] = for {
     ts <- rdf.triplesWithSubjectPredicate(n1,p).compile.toList
    } yield ts.map(_.obj).contains(n2)
}
case class Inv(p: IRI) extends PropPath {
    override def reach(n1: RDFNode, 
                       n2: RDFNode, rdf: RDFReader
                       ): IO[Boolean] = for {
     ts <- rdf.triplesWithSubjectPredicate(n2,p).compile.toList
    } yield ts.map(_.obj).contains(n1)
}
case class Sequ(pp1: PropPath, pp2: PropPath) extends PropPath {
    override def reach(n1: RDFNode, 
                       n2: RDFNode, rdf: RDFReader
                       ): IO[Boolean] =
      IO(false)

}
case class Alt(pp1: PropPath, pp2: PropPath) extends PropPath {
    override def reach(n1: RDFNode, 
                       n2: RDFNode, rdf: RDFReader
                       ): IO[Boolean] =
      IO(false)

}
case class ZeroOrMore(pp: PropPath) extends PropPath {
    override def reach(n1: RDFNode, 
                       n2: RDFNode, rdf: RDFReader
                       ): IO[Boolean] =
      IO(false)

}
case class NoPreds(preds: Set[IRI]) extends PropPath {
    override def reach(n1: RDFNode, 
                       n2: RDFNode, rdf: RDFReader
                       ): IO[Boolean] =
      IO(false)

}

object PropPath {
    def oneOrMore(pp: PropPath): PropPath = Sequ(pp, ZeroOrMore(pp))
} 
Example 199
Source File: InferredSchema.scala    From shaclex   with MIT License 5 votes vote down vote up
package es.weso.schemaInfer
import es.weso.rdf.{PrefixMap, RDFReader}
import es.weso.rdf.nodes.IRI
import es.weso.shex.{Schema, ShapeExpr}
import cats.data._
import cats.implicits._
import cats.effect.IO

case class InferredSchema(smap: Map[IRI, InferredShape]) extends AnyVal {

  def get(label: IRI): Option[InferredShape] =
    smap.get(label)

  def updated(label: IRI, shape: InferredShape): InferredSchema =
    InferredSchema(smap.updated(label,shape))

  def values: List[InferredShape] = smap.values.toList

  type ES[A] = Either[String,A]

  def toShExSchema(rdf: RDFReader,
                   opts: InferOptions,
                   pm: PrefixMap
                  ): EitherT[IO,String, Schema] = { 
  val rs: List[EitherT[IO,String,ShapeExpr]] = 
    smap.toList.map { case (iri, is) => is.toShapeExpr(Some(iri),opts, rdf) }                    
  for {
    es <- rs.sequence
  } yield Schema(IRI(""), Some(pm), None, None, None, Some(es),None,List())
 }
}

object InferredSchema {
  def empty: InferredSchema = InferredSchema(Map())
} 
Example 200
Source File: RDF2SGraph.scala    From shaclex   with MIT License 5 votes vote down vote up
package es.weso.rdf.sgraph

import cats.data._
import cats.implicits._
import cats.effect.IO
import es.weso.rdf.PREFIXES.`rdf:type`
import es.weso.rdf.nodes.{IRI, RDFNode}
import es.weso.rdf.triples.RDFTriple
import es.weso.rdf.{PrefixMap, RDFReader}

object RDF2SGraph {

  type Label = String
  type HRef = String
  type S[A] = State[SGraph, A]
  type Converter[A] = EitherT[S,String,A]

  def rdfTriple2Edge(t: RDFTriple, pm: PrefixMap): Converter[Edge] = for {
    n1 <- rdfNode2Node(t.subj, pm)
    n2 <- rdfNode2Node(t.obj, pm)
    e <- predicate2href(t.pred, pm)
  } yield Edge(n1,n2,e._1, e._2)

  def predicate2href(pred: IRI, pm: PrefixMap): Converter[(Label, HRef)] = pred match {
    case `rdf:type` => ok(("a", pred.str))
    case _ => ok((pm.qualify(pred), pred.str))
  }

  def rdfNode2Node(node: RDFNode, pm: PrefixMap): Converter[Node] = for {
    g <- getGraph
    (g1,n) = g.addNode(node,pm)
    _ <- setGraph(g1)
  } yield n

  def getGraph: Converter[SGraph] = EitherT.liftF[S,String,SGraph](StateT.get)
  def setGraph(g: SGraph): Converter[Unit] = EitherT.liftF[S,String,Unit](StateT.set(g))
  def ok[A](x:A): Converter[A] = EitherT.liftF(StateT.pure(x))
  def err[A](s: String): Converter[A] = EitherT.fromEither(s.asLeft[A])

  def rdf2sgraph(rdf: RDFReader): IO[SGraph] = {
    val pm = rdf.getPrefixMap()
    def cmb(u:Unit, t: RDFTriple): Converter[Unit] = for {
      edge <- rdfTriple2Edge(t, pm)
      g <- getGraph
      g1 = g.addEdge(edge)
      _ <- setGraph(g1)
    } yield ()

    for {
      ts <- rdf.rdfTriples.compile.toList
    } yield {
      ts.toList.foldM(())(cmb).value.run(SGraph.empty).value._1
    }
  }

}