pureconfig module
Import
import refined4s.modules.pureconfig.derivation.types.all.given
import refined4s.modules.pureconfig.derivation.*
Use Drived Instances for Pre-defined Types
To make Newtype, Refined and InlinedRefined have ConfigReader and ConfigWriter type-class instances derived from the actual values, you can simply use
import refined4s.modules.pureconfig.derivation.types.all.given
This works only when the actual type already has ConfigReader and ConfigWriter.
Using refined4s.modules.pureconfig.derivation.types.all.given is required only when ConfigReader and/or ConfigWriter is required for the pre-defined types.
So if you want your Newtype or Refined or InlinedRefined to have ConfigReader and ConfigWriter instances,
you can use pre-defined traits for pureconfig or the deriving method instead.
import refined4s.*
import refined4s.types.all.*
import com.typesafe.config.*
import pureconfig.generic.derivation.default.*
import pureconfig.*
import scala.jdk.CollectionConverters.*
With derivation.types.all,
import refined4s.modules.pureconfig.derivation.types.all.given
final case class NewtypeApiConfig(api: NewtypeApiConfig.Api) derives ConfigReader
object NewtypeApiConfig {
  
  final case class Api(
    id: PosLong,
    baseUri: Uri,
    endpointPath: NonEmptyString,
    additionalId: PosLong,
  ) derives ConfigReader
}
val confString =
  raw"""api {
       |  id = 123
       |  base-uri = "https://localhost:8080"
       |  endpoint-path = "/v1/blah/blah"
       |  additional-id = 999
       |}
       |""".stripMargin
// confString: String = """api {
//   id = 123
//   base-uri = "https://localhost:8080"
//   endpoint-path = "/v1/blah/blah"
//   additional-id = 999
// }
// """
ConfigSource.string(confString).load[NewtypeApiConfig]
// res1: Either[ConfigReaderFailures, NewtypeApiConfig] = Right(
//   value = NewtypeApiConfig(
//     api = Api(
//       id = 123L,
//       baseUri = "https://localhost:8080",
//       endpointPath = "/v1/blah/blah",
//       additionalId = 999L
//     )
//   )
// )
With Explicit Pre-defined Pureconfig Support
There are the following pre-defined traits to support pureconfig ConfigReader and ConfigWriter.
- refined4s.modules.pureconfig.derivation.PureconfigConfigWriter
- refined4s.modules.pureconfig.derivation.PureconfigNewtypeConfigReader
- refined4s.modules.pureconfig.derivation.PureconfigRefinedConfigReader
This works only when the actual type already has  ConfigReader and ConfigWriter.
import refined4s.*
import refined4s.types.all.*
import refined4s.modules.pureconfig.derivation.*
import refined4s.modules.pureconfig.derivation.types.all.given
import com.typesafe.config.*
import pureconfig.generic.derivation.default.*
import pureconfig.*
import scala.jdk.CollectionConverters.*
final case class NewtypeApiConfig(api: NewtypeApiConfig.Api) derives ConfigReader
object NewtypeApiConfig {
  
  final case class Api(
    id: Api.Id,
    baseUri: Api.NewtypeBaseUri,
    endpointPath: Api.RefinedEndpointPath,
    additionalId: Api.InlinedRefinedNewtypeId,
  ) derives ConfigReader
  object Api {
    type Id = Id.Type
    object Id extends InlinedRefined[Long], PureconfigRefinedConfigReader[Long] {
      override inline val inlinedExpectedValue = "a positive Long"
      override inline def invalidReason(a: Long): String =
        "It must be a positive Long"
      override inline def predicate(a: Long): Boolean = a > 0L
      override inline def inlinedPredicate(inline a: Long): Boolean = a > 0L
    }
    type NewtypeBaseUri = NewtypeBaseUri.Type
    object NewtypeBaseUri extends Newtype[Uri], PureconfigNewtypeConfigReader[Uri]
    type RefinedEndpointPath = RefinedEndpointPath.Type
    object RefinedEndpointPath extends Refined[String], PureconfigRefinedConfigReader[String] {
      override inline def invalidReason(a: String): String =
        "It must be a non-empty String"
      override inline def predicate(a: String): Boolean = a != ""
    }
    type InlinedRefinedNewtypeId = InlinedRefinedNewtypeId.Type
    object InlinedRefinedNewtypeId extends Newtype[PosLong], PureconfigNewtypeConfigReader[PosLong]
  }
}
val confString =
  raw"""api {
       |  id = 123
       |  base-uri = "https://localhost:8080"
       |  endpoint-path = "/v1/blah/blah"
       |  additional-id = 999
       |}
       |""".stripMargin
// confString: String = """api {
//   id = 123
//   base-uri = "https://localhost:8080"
//   endpoint-path = "/v1/blah/blah"
//   additional-id = 999
// }
// """
ConfigSource.string(confString).load[NewtypeApiConfig]
// res3: Either[ConfigReaderFailures, NewtypeApiConfig] = Right(
//   value = NewtypeApiConfig(
//     api = Api(
//       id = 123L,
//       baseUri = "https://localhost:8080",
//       endpointPath = "/v1/blah/blah",
//       additionalId = 999L
//     )
//   )
// )
With deriving Method
If you want to have explicit ConfigReader and ConfigWriter type-class instances in your Newtype or Refined or InlinedRefined, you can use the deriving method.
This works only when the actual type already has ConfigReader and ConfigWriter.
import refined4s.*
import refined4s.types.all.*
import refined4s.modules.pureconfig.derivation.*
import refined4s.modules.pureconfig.derivation.types.all.given
import com.typesafe.config.*
import pureconfig.generic.derivation.default.*
import pureconfig.*
import scala.jdk.CollectionConverters.*
final case class NewtypeApiConfig(api: NewtypeApiConfig.Api) derives ConfigReader
object NewtypeApiConfig {
  
  final case class Api(
    id: Api.Id,
    baseUri: Api.NewtypeBaseUri,
    endpointPath: Api.RefinedEndpointPath,
    additionalId: Api.InlinedRefinedNewtypeId,
  ) derives ConfigReader
  object Api {
    type Id = Id.Type
    object Id extends InlinedRefined[Long] {
      override inline val inlinedExpectedValue = "a positive Long"
      override inline def invalidReason(a: Long): String =
        "It must be a positive Long"
      override inline def predicate(a: Long): Boolean = a > 0L
      override inline def inlinedPredicate(inline a: Long): Boolean = a > 0L
      
      given configReaderId: ConfigReader[Id] = deriving[ConfigReader]
    }
    type NewtypeBaseUri = NewtypeBaseUri.Type
    object NewtypeBaseUri extends Newtype[Uri] {
      given configReaderNewtypeBaseUri: ConfigReader[NewtypeBaseUri] = deriving[ConfigReader]
    }
    type RefinedEndpointPath = RefinedEndpointPath.Type
    object RefinedEndpointPath extends Refined[String] {
      override inline def invalidReason(a: String): String =
        "It must be a non-empty String"
      override inline def predicate(a: String): Boolean = a != ""
      
      given configReaderRefinedEndpointPath: ConfigReader[RefinedEndpointPath] = deriving[ConfigReader]
    }
    type InlinedRefinedNewtypeId = InlinedRefinedNewtypeId.Type
    object InlinedRefinedNewtypeId extends Newtype[PosLong] {
      given configReaderInlinedRefinedNewtypeId: ConfigReader[InlinedRefinedNewtypeId] = deriving[ConfigReader]
    }
  }
}
val confString =
  raw"""api {
       |  id = 123
       |  base-uri = "https://localhost:8080"
       |  endpoint-path = "/v1/blah/blah"
       |  additional-id = 999
       |}
       |""".stripMargin
// confString: String = """api {
//   id = 123
//   base-uri = "https://localhost:8080"
//   endpoint-path = "/v1/blah/blah"
//   additional-id = 999
// }
// """
ConfigSource.string(confString).load[NewtypeApiConfig]
// res5: Either[ConfigReaderFailures, NewtypeApiConfig] = Right(
//   value = NewtypeApiConfig(
//     api = Api(
//       id = 123L,
//       baseUri = "https://localhost:8080",
//       endpointPath = "/v1/blah/blah",
//       additionalId = 999L
//     )
//   )
// )