All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pureconfig.module.catseffect2.package.scala Maven / Gradle / Ivy

package pureconfig.module

import java.io.OutputStream
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path}

import scala.language.higherKinds
import scala.reflect.ClassTag

import cats.data.EitherT
import cats.effect.{Blocker, ContextShift, Resource, Sync}
import cats.implicits._
import com.typesafe.config.ConfigRenderOptions

import pureconfig._
import pureconfig.error.ConfigReaderException

package object catseffect2 {

  /** Load a configuration of type `A` from a config source
    *
    * @param cs
    *   the config source from where the configuration will be loaded
    * @param blocker
    *   the blocking context which will be used to load the configuration.
    * @return
    *   The returned action will complete with `A` if it is possible to create an instance of type `A` from the
    *   configuration source, or fail with a ConfigReaderException which in turn contains details on why it isn't
    *   possible
    */
  def loadF[F[_]: Sync: ContextShift, A](
      cs: ConfigSource,
      blocker: Blocker
  )(implicit reader: ConfigReader[A], ct: ClassTag[A]): F[A] =
    EitherT(blocker.delay(cs.cursor()))
      .subflatMap(reader.from)
      .leftMap(ConfigReaderException[A])
      .rethrowT

  /** Load a configuration of type `A` from the standard configuration files
    *
    * @param blocker
    *   the blocking context which will be used to load the configuration.
    * @return
    *   The returned action will complete with `A` if it is possible to create an instance of type `A` from the
    *   configuration files, or fail with a ConfigReaderException which in turn contains details on why it isn't
    *   possible
    */
  def loadConfigF[F[_]: Sync: ContextShift, A: ConfigReader](blocker: Blocker)(implicit ct: ClassTag[A]): F[A] =
    loadF(ConfigSource.default, blocker)

  /** Save the given configuration into a property file
    *
    * @param conf
    *   The configuration to save
    * @param outputPath
    *   Where to write the configuration
    * @param blocker
    *   the blocking context which will be used to load the configuration.
    * @param overrideOutputPath
    *   Override the path if it already exists
    * @param options
    *   the config rendering options
    * @return
    *   The return action will save out the supplied configuration upon invocation
    */
  def blockingSaveConfigAsPropertyFileF[F[_]: ContextShift, A: ConfigWriter](
      conf: A,
      outputPath: Path,
      blocker: Blocker,
      overrideOutputPath: Boolean = false,
      options: ConfigRenderOptions = ConfigRenderOptions.defaults()
  )(implicit F: Sync[F]): F[Unit] = {
    val fileAlreadyExists =
      F.raiseError[Unit](
        new IllegalArgumentException(s"Cannot save configuration in file '$outputPath' because it already exists")
      )

    val fileIsDirectory =
      F.raiseError[Unit](
        new IllegalArgumentException(s"Cannot save configuration in file '$outputPath' because it is a directory")
      )

    val check =
      F.defer {
        if (!overrideOutputPath && Files.isRegularFile(outputPath)) fileAlreadyExists
        else if (Files.isDirectory(outputPath)) fileIsDirectory
        else F.unit
      }

    val outputStream =
      Resource.make(blocker.delay(Files.newOutputStream(outputPath))) { os =>
        blocker.delay(os.close())
      }

    blocker.blockOn(check) >> outputStream.use { os =>
      blockingSaveConfigToStreamF(conf, os, blocker, options)
    }
  }

  /** Writes the configuration to the output stream and closes the stream
    *
    * @param conf
    *   The configuration to write
    * @param outputStream
    *   The stream in which the configuration should be written
    * @param blocker
    *   the blocking context which will be used to load the configuration.
    * @param options
    *   the config rendering options
    * @return
    *   The return action will save out the supplied configuration upon invocation
    */
  def blockingSaveConfigToStreamF[F[_]: ContextShift, A](
      conf: A,
      outputStream: OutputStream,
      blocker: Blocker,
      options: ConfigRenderOptions = ConfigRenderOptions.defaults()
  )(implicit F: Sync[F], writer: ConfigWriter[A]): F[Unit] =
    F.delay(writer.to(conf)).map { rawConf =>
      // HOCON requires UTF-8:
      // https://github.com/lightbend/config/blob/master/HOCON.md#unchanged-from-json
      StandardCharsets.UTF_8.encode(rawConf.render(options)).array
    } flatMap { bytes =>
      blocker.delay {
        outputStream.write(bytes)
        outputStream.flush()
      }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy