Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Dec 10 12:25

    vlovgr on master

    Update sbt-scalafmt to 2.3.0 Merge pull request #286 from sc… (compare)

  • Dec 10 12:25
    vlovgr closed #286
  • Dec 10 04:56
    codecov[bot] commented #286
  • Dec 10 04:50
    scala-steward opened #286
  • Dec 06 16:03
    Krever commented #284
  • Dec 06 15:59
    codecov[bot] commented #284
  • Dec 06 15:59
    Krever synchronize #284
  • Dec 06 14:37

    vlovgr on configdecoder-as

    (compare)

  • Dec 06 14:37

    vlovgr on master

    Add ConfigDecoder#as Merge pull request #285 from vl… (compare)

  • Dec 06 14:37
    vlovgr closed #285
  • Dec 06 14:27
    codecov[bot] commented #285
  • Dec 06 14:22
    vlovgr opened #285
  • Dec 06 14:22

    vlovgr on configdecoder-as

    Add ConfigDecoder#as (compare)

  • Dec 06 13:46
    codecov[bot] commented #284
  • Dec 06 13:39
    codecov[bot] commented #284
  • Dec 06 13:39
    Krever synchronize #284
  • Dec 06 13:36
    codecov[bot] commented #284
  • Dec 06 13:31
    Krever edited #284
  • Dec 06 13:31
    Krever opened #284
  • Dec 05 21:48

    vlovgr on master

    Update sbt-mdoc to 2.0.3 Merge pull request #283 from sc… (compare)

Gabriel Volpe
@gvolpe
I managed to get it working with the new version but I'm not really happy with the result, this is what I had to do:
implicit def showCoercible[A: Coercible[NonEmptyString, ?]]: Show[A] =
   new Show[A] {
     def show(t: A): String = t.repr.asInstanceOf[NonEmptyString].value
   }

implicit def bar[A: Coercible[NonEmptyString, ?]: Show]: ConfigDecoder[String, Secret[A]] =
   ConfigDecoder[String, String].mapEither(
     (_, x) => refineV[NonEmpty](x).map(s => Secret(s.coerce[A])).leftMap(e => ConfigError(e))
   )
Any suggestions? :)
Viktor Lövgren
@vlovgr
@gvolpe Instead of .as[Secret[NonEmptyString]] use .as[NonEmptyString].secret.
.secret also redacts sensitive details in errors, something decoders cannot do.
You'll probably need import eu.timepit.refined.cats._ with refined-cats as well.
Gabriel Volpe
@gvolpe
Awesome, didn't know about that! I'll give it a try when I get back to my laptop, thanks!
Gabriel Volpe
@gvolpe
Worked like a charm and I was able to remove all the custom instances, loving it so far :tada:
Viktor Lövgren
@vlovgr
Glad to hear @gvolpe! :tada:
For those of you tranisitioning from 0.x, there's a bit more detail in this recent blog post: https://cir.is/blog/2019/11/01/ciris-v1.0.0
Gabriel Volpe
@gvolpe
Nice, it really looks much better now :)
David Francoeur
@daddykotex
Hey, thanks for the lib :)
From my discussion with @systemfw on the cats gitter, I can't stop myself from thinking that there must be a better way to that

Given:

class T
object T {
  def fromInputstream(is: InputStream): T = ???
}

There must be a better way to build T from an env var:

val configT: ConfigValue[T] = {
  val name = "THE_ENV_VAR"
  ConfigValue.suspend {
    val key = ConfigKey.env(name)
    val value = System.getenv(name)
    val acquire = IO.delay { new ByteArrayInputStream(value.getBytes) }
    val release = (is: InputStream) => IO { is.close }
    val unsafeValue = Resource
      .make[IO, InputStream](acquire)(release)
      .use { is =>
        IO { T.fromInputstream(is) }
      }
      .unsafeRunSync
    ConfigValue.loaded(key, unsafeValue)
  }
}

Ideally I would have something like that:

def safeT(value: String): IO[T] = {
  val acquire = IO.delay { new ByteArrayInputStream(value.getBytes) }
  val release = (is: InputStream) => IO { is.close }
  Resource
    .make[IO, InputStream](acquire)(release)
    .use { is =>
      IO { T.fromInputstream(is) }
    }
}
env("THE_ENV_VAR").evalMap(safeT)
Viktor Lövgren
@vlovgr
@daddykotex Yes, I think we have to go through async and require Effect: vlovgr/ciris#272
Viktor Lövgren
@vlovgr
@daddykotex ConfigValue#evalMap from #272 is available in v1.0.1.
David Francoeur
@daddykotex
holy moly!
David Francoeur
@daddykotex
thank you very much
Wojtek Pituła
@Krever
Hey, I was wondering: why ConfigValue is a FlatMap and Apply but not a Monad? It clearly shows that pure is not achievable but why?
Viktor Lövgren
@vlovgr
@Krever really great question! You might also have noticed that we have ConfigValue.default with the same signature as pure (excluding the by-name).
It is technically possible to have a Monad, but pure cannot be ConfigValue.default to get the right semantics.
Instead, pure has to be what would be ConfigValue.loaded(value), i.e. the current loaded but without any key.
So now the user basically has to choose between ConfigValue.default(value) and ConfigValue.loaded(value).
The main difference is that loaded means we won't be able to use defaults later in our composition.
Viktor Lövgren
@vlovgr
So techincally, we could have a Monad and expose ConfigValue.loaded(value), but it puts a burden on the user: should I use ConfigValue.default, ConfigValue.loaded, and what does pure mean here?
Instead, I went for a simpler approach: anything not a ConfigValue.loaded(key, value) is a default value, and the only way to construct it is with ConfigValue.default.
Wojtek Pituła
@Krever
I see! In fact I would also assume pure to be default and so it might be confusing. Thanks for the detailed answer
Another question: have you considered abandoning FlatMap and adding ability to generate some sort of docs? With flatmap in place its not technically possible. Im not saying its something worth doing, just wondering if it was considered. Similarly to --help in decline
Viktor Lövgren
@vlovgr
(Regarding the second part of the question: the reason why ConfigValue.Par only has an Apply instance and not e.g. FlatMap, is because the flatMap consistent with ap cannot be expressed using tailRecM. In v1.0.2 we have ConfigValue#parFlatMap with that exact flatMap though, because it's useful in certain situations, see here: https://github.com/vlovgr/ciris/pull/273.)
Viktor Lövgren
@vlovgr
@Krever There was this pull request before 1.x: vlovgr/ciris#257. You're correct that we can't go beyond Applicative if we want to generate documentation in the general case. I find flatMap to have a clear and useful purpose (i.e. affect configuration loading depending on an earlier value), and so there's a good reason for it to exist. I think generating documentation is tempting, but not tempting enough to give up flatMap.
It should still be possible to generate documentation, but it won't work across flatMaps of course (and so might be surprising in that case).
Not ideal, but if you limit yourself to Applicative when writing the configuration, it should still do the right thing.
Wojtek Pituła
@Krever
Thanks :) Now it reminds me there was some new typeclass coming out of research, standing between Monad and Applicative. I have to check it out and see how it plays with graph introspection (which is basically what we need for docs gen).
Viktor Lövgren
@vlovgr
Are you thinking of Selective?
Wojtek Pituła
@Krever
yep
Viktor Lövgren
@vlovgr
Also very interested to see how we can make use of it. :)
Matheus Hoffmann
@Hoffmannxd
Hello folks, i'm constructing some custom configuration, ConfigValue instances for list of url/ips usually when connecting to distributed brokers like Cassandra/Kafka .... I did in this way, it worked, but can i create a pure config value in some way? The unique way that i found was using .default method.
  type HostList = NonEmpty And Forall[Or[IPv4, Url]]

  final val hostRefined: String => ConfigValue[List[String]] = string => {
    refineV[HostList](string.split(",").toList.map(_.replace(" ", "")))
      .fold(
        error => ConfigValue.failed[List[String]](ConfigError(error)),
        correct => ConfigValue.default[List[String]](correct.value)
      )
  }
Viktor Lövgren
@vlovgr
@Hoffmannxd when decoding types you most often only want to use .as and write a custom ConfigDecoder if needed.
Something like the following should work.
import $ivy.`is.cir::ciris-refined:1.0.2`, cats.implicits._, ciris._, refined._
import eu.timepit.refined._, api._, boolean._, collection._, string._

type HostList = List[String] Refined (NonEmpty And Forall[Or[IPv4, Url]])

implicit val hostListConfigDecoder: ConfigDecoder[String, HostList] = 
  ConfigDecoder
    .identity[String]
    .map(_.split(",").toList.map(_.replace(" ", "")))
    .mapEither(ConfigDecoder[List[String], HostList].decode)

env("KAFKA_HOSTS").as[HostList]
Note the ConfigDecoder[List[String], HostList] comes from import ciris.refined._ (with the ciris-refined module).
We should probably add a convenience function for that last mapEither, so we could say e.g. mapDecode[HostList] instead.
Matheus Hoffmann
@Hoffmannxd
Thanks @vlovgr , this solution seems much more cleaner.
Wojtek Pituła
@Krever
Hey @vlovgr, would you be interested in incorporating ciris-hocon into ciris repo? I could contact author to check if thats ok. Currently cirisi-hocon is on ciris 0.12 and I can imagine it would be more maintanable in a long term to keep it together with ciris. In the end the integration is rather small, single file: https://github.com/2m/ciris-hocon/blob/master/src/main/scala/Hocon.scala
Viktor Lövgren
@vlovgr
Yes, great idea @Krever! :+1:
I believe @2m is in the channel, not sure if he's checking notifications though. :)
Wojtek Pituła
@Krever
I started to migrate the codebase and ended up reimplementing it from scratch with user-api similarities. I will try to make a PR to discuss what would be nice to have there
Viktor Lövgren
@vlovgr
:+1:
Wojtek Pituła
@Krever
Martynas Mickevičius
@2m
I am glad to see hocon support land to ciris. Thanks for pushing it @Krever !