├── .editorconfig ├── .github ├── stale.yml └── workflows │ ├── master.yml │ ├── pr.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── Ci.kt ├── changelog.md ├── example-maven ├── maven.pom └── src │ └── main │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── example │ │ └── Config.kt │ └── resources │ └── application.json ├── example-native ├── META-INF │ └── native-image │ │ ├── generated │ │ ├── jni-config.json │ │ ├── predefined-classes-config.json │ │ ├── proxy-config.json │ │ ├── reflect-config.json │ │ ├── resource-config.json │ │ └── serialization-config.json │ │ └── kotlin-resource.json ├── README.md ├── build.gradle.kts ├── config.yaml ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── sksamuel │ └── hoplite │ └── example │ └── Main.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hoplite-arrow ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── decoder │ │ │ └── arrow │ │ │ ├── OptionDecoder.kt │ │ │ ├── nonEmptyList.kt │ │ │ └── tuples.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── arrow │ │ ├── NonEmptyListTest.kt │ │ ├── OptionTest.kt │ │ └── TupleDecoderTest.kt │ └── resources │ ├── options.yml │ ├── test_nel.yml │ ├── test_tuple_4.json │ ├── test_tuple_5.json │ ├── test_tuples.json │ ├── test_tuples.toml │ └── test_tuples.yml ├── hoplite-aws-kotlin ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── aws │ │ │ └── kotlin │ │ │ ├── AbstractAwsSecretsManagerContextResolver.kt │ │ │ ├── AwsOps.kt │ │ │ ├── AwsSecretsManagerContextResolver.kt │ │ │ ├── AwsSecretsManagerPreprocessor.kt │ │ │ ├── ParameterStorePathPreprocessor.kt │ │ │ ├── ParameterStorePathPropertySource.kt │ │ │ ├── ParameterStorePreprocessor.kt │ │ │ └── RegionDecoder.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── aws │ │ └── kotlin │ │ ├── AwsSecretsManagerContextResolverTest.kt │ │ ├── AwsSecretsManagerPreprocessorTest.kt │ │ └── RegionDecoderTest.kt │ └── resources │ ├── logback.xml │ ├── multiple_secrets.props │ ├── secrets.props │ └── ssm.props ├── hoplite-aws ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── aws │ │ │ ├── AbstractAwsSecretsManagerContextResolver.kt │ │ │ ├── AwsOps.kt │ │ │ ├── AwsSecretsManagerContextResolver.kt │ │ │ ├── AwsSecretsManagerPreprocessor.kt │ │ │ ├── ParameterStorePathPreprocessor.kt │ │ │ ├── ParameterStorePathPropertySource.kt │ │ │ ├── ParameterStorePreprocessor.kt │ │ │ └── RegionDecoder.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── aws │ │ ├── AwsSecretsManagerContextResolverTest.kt │ │ ├── AwsSecretsManagerPreprocessorTest.kt │ │ └── RegionDecoderTest.kt │ └── resources │ ├── logback.xml │ ├── multiple_secrets.props │ ├── secrets.props │ └── ssm.props ├── hoplite-aws2 ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── aws2 │ │ │ ├── AwsSecretsManagerContextResolver.kt │ │ │ ├── AwsSecretsManagerPreprocessor.kt │ │ │ ├── AwsSecretsManagerSource.kt │ │ │ └── RegionDecoder.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ └── kotlin │ └── com │ └── sksamuel │ └── hoplite │ └── aws2 │ ├── AwsSecretsManagerPreprocessorTest.kt │ ├── AwsSecretsManagerSourceTest.kt │ └── RegionDecoderTest.kt ├── hoplite-azure ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── sksamuel │ └── hoplite │ └── azure │ ├── AzureKeyVaultContextResolver.kt │ ├── AzureKeyVaultPreprocessor.kt │ └── AzureOps.kt ├── hoplite-consul ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── sksamuel │ └── hoplite │ └── consul │ ├── ConsulConfigPreprocessor.kt │ └── ConsulConfigResolver.kt ├── hoplite-core ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ ├── ClasspathResourceLoader.kt │ │ │ ├── ConfigBinder.kt │ │ │ ├── ConfigFailure.kt │ │ │ ├── ConfigLoader.kt │ │ │ ├── ConfigLoaderBuilder.kt │ │ │ ├── ConfigLoaderBuilderExtensions.kt │ │ │ ├── ConfigSource.kt │ │ │ ├── Constants.kt │ │ │ ├── DecoderContext.kt │ │ │ ├── ParameterMapper.kt │ │ │ ├── Pos.kt │ │ │ ├── PropertySource.kt │ │ │ ├── Reporter.kt │ │ │ ├── decoder │ │ │ ├── Base64Decoder.kt │ │ │ ├── BigDecimalDecoder.kt │ │ │ ├── BigIntegerDecoder.kt │ │ │ ├── BooleanArrayDecoder.kt │ │ │ ├── ByteArrayDecoder.kt │ │ │ ├── DataClassDecoder.kt │ │ │ ├── Decoder.kt │ │ │ ├── DecoderRegistry.kt │ │ │ ├── InetAddressDecoder.kt │ │ │ ├── InlineClassDecoder.kt │ │ │ ├── KClassDecoder.kt │ │ │ ├── LinkedHashMapDecoder.kt │ │ │ ├── ListDecoder.kt │ │ │ ├── LocaleDecoder.kt │ │ │ ├── MapDecoder.kt │ │ │ ├── MinutesDecoder.kt │ │ │ ├── PrincipalDecoder.kt │ │ │ ├── PropsDecoder.kt │ │ │ ├── RegexDecoder.kt │ │ │ ├── SealedClassDecoder.kt │ │ │ ├── SecondsDecoder.kt │ │ │ ├── SecretDecoder.kt │ │ │ ├── SetDecoder.kt │ │ │ ├── SizeInBytesDecoder.kt │ │ │ ├── SortedSetDecoder.kt │ │ │ ├── dates.kt │ │ │ ├── enum.kt │ │ │ ├── files.kt │ │ │ ├── metric.kt │ │ │ ├── pairs.kt │ │ │ ├── primitives.kt │ │ │ ├── ranges.kt │ │ │ ├── urls.kt │ │ │ └── uuid.kt │ │ │ ├── env │ │ │ └── Environment.kt │ │ │ ├── experimental.kt │ │ │ ├── fp.kt │ │ │ ├── fp │ │ │ ├── NonEmptyList.kt │ │ │ └── Validated.kt │ │ │ ├── internal │ │ │ ├── ConfigParser.kt │ │ │ ├── DecodeModeValidator.kt │ │ │ ├── Decoding.kt │ │ │ ├── Preprocessing.kt │ │ │ ├── PropertySourceLoader.kt │ │ │ ├── UnresolvedSubstitutionChecker.kt │ │ │ └── cascade.kt │ │ │ ├── nodes.kt │ │ │ ├── parsers │ │ │ ├── Parser.kt │ │ │ ├── PropsParser.kt │ │ │ └── loadProps.kt │ │ │ ├── preprocessor │ │ │ ├── EnvOrSystemPropertyPreprocessor.kt │ │ │ ├── LookupPreprocessor.kt │ │ │ ├── Preprocessor.kt │ │ │ ├── PropsPreprocessor.kt │ │ │ └── RandomPreprocessor.kt │ │ │ ├── report │ │ │ └── Reporter.kt │ │ │ ├── resolver │ │ │ ├── Resolver.kt │ │ │ ├── Resolving.kt │ │ │ ├── context │ │ │ │ ├── ContextResolver.kt │ │ │ │ ├── ContextResolverMode.kt │ │ │ │ ├── EnvVarContextResolver.kt │ │ │ │ ├── HopliteContextResolver.kt │ │ │ │ ├── ManifestContextResolver.kt │ │ │ │ ├── RandomContextResolver.kt │ │ │ │ ├── ReferenceContextResolver.kt │ │ │ │ ├── SystemContextResolver.kt │ │ │ │ └── SystemPropertyContextResolver.kt │ │ │ └── validator │ │ │ │ ├── HostnameValidator.kt │ │ │ │ ├── JdbcHostnameValidator.kt │ │ │ │ └── PortValidator.kt │ │ │ ├── secrets │ │ │ ├── Obfuscator.kt │ │ │ └── SecretsPolicy.kt │ │ │ ├── sources │ │ │ ├── CommandLinePropertySource.kt │ │ │ ├── ConfigFilePropertySource.kt │ │ │ ├── EnvironmentVariablesPropertySource.kt │ │ │ ├── InputStreamPropertySource.kt │ │ │ ├── MapPropertySource.kt │ │ │ ├── SystemPropertiesPropertySource.kt │ │ │ ├── UserSettingsPropertySource.kt │ │ │ └── XdgConfigPropertySource.kt │ │ │ ├── time │ │ │ ├── Durations.kt │ │ │ └── Period.kt │ │ │ ├── transformer │ │ │ ├── NodeTransformer.kt │ │ │ └── PathNormalizer.kt │ │ │ └── types.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ ├── com.sksamuel.hoplite.decoder.Decoder │ │ └── com.sksamuel.hoplite.parsers.Parser │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ ├── CascadeTest.kt │ │ ├── CascadingNormalizationTest.kt │ │ ├── EmptyDecoderRegistryTest.kt │ │ ├── EnvironmentVariablesPropertySourceTest.kt │ │ ├── Github350.kt │ │ ├── Github389.kt │ │ ├── InvalidConstructorArgsTest.kt │ │ ├── KebabCaseParamMapperTest.kt │ │ ├── LoadPropsTest.kt │ │ ├── NoValuesTest.kt │ │ ├── PrefixTest.kt │ │ ├── PropertySourceTest.kt │ │ ├── PropsParserTest.kt │ │ ├── ReporterTest.kt │ │ ├── SnakeCaseParamMapperTest.kt │ │ ├── SystemPropertiesPropertySourceTest.kt │ │ ├── WithoutDefaultsRegistryTest.kt │ │ ├── decoder │ │ ├── BooleanArrayDecoderTest.kt │ │ ├── ByteArrayDecoderTest.kt │ │ ├── CustomDataClassDecoderTest.kt │ │ ├── DataClassDecoderTest.kt │ │ ├── EnumDecoderTest.kt │ │ ├── InlineClassDecoderTest.kt │ │ ├── LocalTimeDecoderTest.kt │ │ ├── SizeInBytesDecoderTest.kt │ │ └── TripleDecoderTest.kt │ │ ├── preprocessor │ │ ├── IterationsTest.kt │ │ └── PropsPreprocessorTest.kt │ │ ├── report │ │ └── PrefixObfuscatorTest.kt │ │ ├── resolver │ │ ├── ContextResolverModeTest.kt │ │ ├── ManifestContextResolverTest.kt │ │ ├── RecursiveResolverTest.kt │ │ ├── ReferenceContextResolverTest.kt │ │ ├── SystemContextResolverTest.kt │ │ ├── SystemPropertyContextResolverTest.kt │ │ └── validator │ │ │ ├── HostnameValidatorTest.kt │ │ │ ├── JdbcHostnameValidatorTest.kt │ │ │ └── PortValidatorTest.kt │ │ └── transformer │ │ └── PathNormalizerTest.kt │ └── resources │ ├── .env.properties │ ├── META-INF │ └── MANIFEST.MF │ ├── a1.properties │ ├── advanced.props │ ├── application.properties │ ├── basic.props │ ├── empty.props │ ├── localtime.props │ ├── prefixProcessor.props │ ├── processme.props │ ├── sample.properties │ ├── test_boolean_array.props │ ├── test_byte_array.props │ └── test_triple.props ├── hoplite-cronutils ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── cronutils │ │ │ └── CronDecoder.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── CronDecoderTest.kt │ └── resources │ └── cron.props ├── hoplite-datetime ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── datetime │ │ │ ├── InstantDecoder.kt │ │ │ ├── LocalDateDecoder.kt │ │ │ └── LocalDateTimeDecoder.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── datetime │ │ ├── InstantDecoderTest.kt │ │ ├── LocalDateDecoderTest.kt │ │ └── LocalDateTimeDecoderTest.kt │ └── resources │ ├── instant.props │ ├── instant_iso.props │ ├── localdate.props │ └── localdatetime.props ├── hoplite-gcp ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── sksamuel │ └── hoplite │ └── gcp │ ├── AbstractGcpSecretManagerContextResolver.kt │ ├── GcpSecretManagerContextResolver.kt │ └── GcpSecretManagerPreprocessor.kt ├── hoplite-hdfs ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── hdfs │ │ └── PathDecoder.kt │ └── resources │ └── META-INF │ └── services │ └── com.sksamuel.hoplite.decoder.Decoder ├── hoplite-hikaricp ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── hikari │ │ │ └── HikariDataSourceDecoder.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── HikariDataSourceTest.kt │ └── resources │ └── hikari.yaml ├── hoplite-hocon ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── hocon │ │ │ └── HoconParser.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.parsers.Parser │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── hocon │ │ ├── FloatingPointTest.kt │ │ ├── HoconEnvOverrideTest.kt │ │ ├── HoconParserTest.kt │ │ ├── InlineClassTest.kt │ │ ├── KebabSnakeCaseMapperTest.kt │ │ ├── SealedClassLinkedHashMapTest.kt │ │ └── SealedClassTest.kt │ └── resources │ ├── basic.conf │ ├── floats.conf │ ├── hocon.conf │ ├── mixed_snake_dash_case.conf │ ├── sealed.conf │ ├── sealed_class_linked_hash_map.conf │ └── valuetype.conf ├── hoplite-javax ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── decoder │ │ └── javax │ │ └── security.kt │ └── resources │ └── META-INF │ └── services │ └── com.sksamuel.hoplite.decoder.Decoder ├── hoplite-json ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── json │ │ │ ├── HopliteModule.kt │ │ │ ├── JsonPropertyParamMapper.kt │ │ │ ├── JsonPropertySource.kt │ │ │ └── json.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.parsers.Parser │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── json │ │ ├── AmbiguousPropertyNameTest.kt │ │ ├── BasicDecodersTest.kt │ │ ├── EnvPropertySourceNormalizationTest.kt │ │ ├── EnvPropertySourceTest.kt │ │ ├── EnvPropertySourceUnderscoreAsSeparatorTest.kt │ │ ├── EnvPropertySourceUppercaseTest.kt │ │ ├── ErrorTests.kt │ │ ├── FileDecoderTest.kt │ │ ├── FileSourceTest.kt │ │ ├── InetAddressDecoderTest.kt │ │ ├── JsonParserTest.kt │ │ ├── JsonPropertyTest.kt │ │ ├── MapTest.kt │ │ ├── PathDecoderTest.kt │ │ ├── PrincipalDecoderTest.kt │ │ ├── SealedClassLinkedHashMapTest.kt │ │ ├── SealedClassTest.kt │ │ ├── SetDecoderTest.kt │ │ ├── SizeInBytesTest.kt │ │ ├── SortedSetDecoderTest.kt │ │ ├── SystemPropertySourceTest.kt │ │ ├── TruthyTest.kt │ │ └── UrlDecoderTest.kt │ └── resources │ ├── basic.json │ ├── basic_with_comments.json │ ├── empty.json │ ├── error1.json │ ├── nested_basic_arrays.json │ ├── nested_container_arrays.json │ ├── principal.json │ ├── sealed_class_linked_hash_map.json │ ├── sealed_test_1.json │ ├── sealed_test_2.json │ ├── sealed_test_3.json │ ├── sets.json │ ├── size_in_bytes.json │ ├── sysproptest1.props │ ├── sysproptest2.json │ ├── test_bigdecimal.json │ ├── test_biginteger.json │ ├── test_file.json │ ├── test_ipnet.json │ ├── test_path.json │ ├── test_urls.json │ ├── truthy_10.json │ ├── truthy_TF.json │ └── truthy_yesno.json ├── hoplite-micrometer-datadog ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── micrometer │ │ └── datadog │ │ └── DatadogConfigDecoder.kt │ └── resources │ └── META-INF │ └── services │ └── com.sksamuel.hoplite.decoder.Decoder ├── hoplite-micrometer-prometheus ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── micrometer │ │ └── prometheus │ │ └── PrometheusConfigDecoder.kt │ └── resources │ └── META-INF │ └── services │ └── com.sksamuel.hoplite.decoder.Decoder ├── hoplite-micrometer-statsd ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── micrometer │ │ └── statsd │ │ └── StatsdConfigDecoder.kt │ └── resources │ └── META-INF │ └── services │ └── com.sksamuel.hoplite.decoder.Decoder ├── hoplite-toml ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── toml │ │ │ └── TomlParser.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.parsers.Parser │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── toml │ │ ├── ConfigAliasNormalizationTest.kt │ │ ├── ConfigAliasTest.kt │ │ ├── DataClassInitializationErrorTest.kt │ │ ├── DurationTest.kt │ │ ├── InetAddressDecoderTest.kt │ │ ├── LocaleDecoderTest.kt │ │ ├── MapTest.kt │ │ ├── PairDecoderTest.kt │ │ ├── PeriodKeyTest.kt │ │ ├── PeriodTest.kt │ │ ├── RegexDecoderTest.kt │ │ ├── SealedClassTest.kt │ │ ├── SecretTest.kt │ │ ├── SerializableClassTest.kt │ │ ├── TomlParserTest.kt │ │ ├── UrlDecoderTest.kt │ │ └── ValueTypeTest.kt │ └── resources │ ├── alias.toml │ ├── alias_normalization.toml │ ├── basic.toml │ ├── duration_as_longs.toml │ ├── key_with_dots_in_map.toml │ ├── masked.toml │ ├── period.toml │ ├── repeated_alias.toml │ ├── serializable.toml │ ├── test_inet.toml │ ├── test_locale.toml │ ├── test_pair.toml │ ├── test_regex.toml │ ├── test_urls.toml │ ├── unused.toml │ ├── value.toml │ ├── versions.toml │ └── versions2.toml ├── hoplite-vault ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── vault │ │ ├── VaultContextResolver.kt │ │ └── VaultSecretPreprocessor.kt │ └── test │ └── kotlin │ └── com │ └── sksamuel │ └── hoplite │ └── vault │ ├── VaultContextResolverTest.kt │ └── VaultSecretPreprocessorTest.kt ├── hoplite-vavr ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── decoder │ │ │ └── vavr │ │ │ ├── ListDecoder.kt │ │ │ ├── MapDecoder.kt │ │ │ ├── OptionDecoder.kt │ │ │ ├── SetDecoder.kt │ │ │ ├── SortedSetDecoder.kt │ │ │ └── tuples.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.decoder.Decoder │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── decoder │ │ └── vavr │ │ ├── ListDecoderTest.kt │ │ ├── MapDecoderTest.kt │ │ ├── OptionDecoderTest.kt │ │ ├── SetDecoderTest.kt │ │ ├── SortedSetDecoderTest.kt │ │ └── TuplesTest.kt │ └── resources │ ├── options.yml │ ├── test_list.yml │ ├── test_map.yml │ ├── test_set.yml │ └── test_tuples.yml ├── hoplite-watch-consul ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── watcher │ │ └── consul │ │ └── consul.kt │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── watcher │ │ └── consul │ │ └── ConsulWatcherTest.kt │ └── resources │ └── consulConfig.yml ├── hoplite-watch ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ └── watch │ │ ├── ReloadableConfig.kt │ │ ├── Watchable.kt │ │ └── watchers │ │ └── file.kt │ └── test │ └── kotlin │ └── com │ └── sksamuel │ └── hoplite │ └── watch │ └── WatcherTest.kt ├── hoplite-yaml ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── sksamuel │ │ │ └── hoplite │ │ │ └── yaml │ │ │ ├── YamlParser.kt │ │ │ └── YamlPropertySource.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.sksamuel.hoplite.parsers.Parser │ └── test │ ├── kotlin │ └── com │ │ └── sksamuel │ │ └── hoplite │ │ ├── decoder │ │ ├── Base64DecoderTest.kt │ │ ├── BasicDecodersTest.kt │ │ ├── CollectionDecodersTest.kt │ │ ├── EnumDecoderTest.kt │ │ ├── FileDecoderTest.kt │ │ ├── InetAddressDecoderTest.kt │ │ ├── NullOverrideTest.kt │ │ ├── PathDecoderTest.kt │ │ ├── PrincipalDecoderTest.kt │ │ ├── PropsDecoderTest.kt │ │ ├── SealedClassDecoderTest.kt │ │ ├── SealedClassDiscriminatorFieldTest.kt │ │ ├── StringSquashTest.kt │ │ ├── UrlDecoderTest.kt │ │ └── ValueTypeDecoderTest.kt │ │ └── yaml │ │ ├── AnchorAliasTest.kt │ │ ├── CollectionErrorTest.kt │ │ ├── DenormalizedLinkedHashMapKeysTest.kt │ │ ├── DenormalizedMapKeysTest.kt │ │ ├── EmptySourceTest.kt │ │ ├── EnvVarContextResolverTest.kt │ │ ├── EnvVarPreprocessorTest.kt │ │ ├── GithubIssue72.kt │ │ ├── InputStreamPropertySourceTest.kt │ │ ├── InstantTest.kt │ │ ├── Issue236Test.kt │ │ ├── KebabCaseTest.kt │ │ ├── KotlinSerializationTest.kt │ │ ├── LinkedHashMapTest.kt │ │ ├── LookupPreprocessorTest.kt │ │ ├── MapTest.kt │ │ ├── MonthDayTest.kt │ │ ├── NestedClassTest.kt │ │ ├── NullTests.kt │ │ ├── ParseErrorTest.kt │ │ ├── RandomPreprocessorTest.kt │ │ ├── ResourceTest.kt │ │ ├── SetDecoderTest.kt │ │ ├── SnakeCaseTest.kt │ │ ├── StrictModeTest.kt │ │ ├── SysPropPreprocessorTest.kt │ │ ├── TruthyTest.kt │ │ └── YamlParserTest.kt │ └── resources │ ├── anchor-merge-base.yml │ ├── anchor.yml │ ├── array.yml │ ├── bad_indent.yml │ ├── base64.yml │ ├── basic.yml │ ├── collections_error.yaml │ ├── empty.yml │ ├── empty_with_comments.yml │ ├── enums.yml │ ├── fallback_1.yml │ ├── fallback_2.yml │ ├── fallback_3.yml │ ├── foo.yml │ ├── github72.yml │ ├── linked_hash_map.yml │ ├── lonely.yml │ ├── month_day.yml │ ├── nested_basic_arrays.yml │ ├── nested_container_arrays.yml │ ├── nulls.yml │ ├── numbers_map.yml │ ├── principal.yaml │ ├── props_decoder.yml │ ├── sealed_class.yml │ ├── sealed_class_list.yml │ ├── sealed_class_with_list_of_objects.yaml │ ├── sealed_class_with_object.yaml │ ├── sealed_class_with_object_invalid_value.yaml │ ├── set_objects.yml │ ├── sets.yml │ ├── snake_case.yml │ ├── test1.yml │ ├── test_array.yml │ ├── test_array_as_delimited_string.yml │ ├── test_bigdecimal.yml │ ├── test_biginteger.yml │ ├── test_data_class_in_map.yaml │ ├── test_date.yml │ ├── test_datetime.yml │ ├── test_duration.yml │ ├── test_duration_iso_8601.yml │ ├── test_enum.yml │ ├── test_env_replacement.yml │ ├── test_env_replacement2.yml │ ├── test_env_replacement3.yml │ ├── test_file.yml │ ├── test_instant.yml │ ├── test_ipnet.yml │ ├── test_lookup.yml │ ├── test_lookup1.yml │ ├── test_lookup2.yml │ ├── test_lookup_gha_style.yml │ ├── test_lookup_react.yml │ ├── test_map.yml │ ├── test_nested2.yml │ ├── test_nested3.yml │ ├── test_null_inline.yml │ ├── test_nulls.yml │ ├── test_path.yml │ ├── test_random_preprocessor.yml │ ├── test_set.yml │ ├── test_set_enum.yml │ ├── test_sysproperty_replacement.yml │ ├── test_undefined.yml │ ├── test_urls.yml │ ├── test_uuid.yml │ ├── truthy_10.yml │ ├── truthy_TF.yml │ ├── truthy_yesno.yml │ └── value_type.yml ├── logo.png ├── publish.gradle.kts └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | indent_style = space 6 | indent_size = 2 7 | max_line_length = 120 8 | 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.gradle.kts] 13 | indent_size = 3 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 60 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - pinned 16 | - "[Status] Maybe Later" 17 | 18 | # Set to true to ignore issues in a project (defaults to false) 19 | exemptProjects: false 20 | 21 | # Set to true to ignore issues in a milestone (defaults to false) 22 | exemptMilestones: false 23 | 24 | # Set to true to ignore issues with an assignee (defaults to false) 25 | exemptAssignees: false 26 | 27 | # Label to use when marking as stale 28 | staleLabel: wontfix 29 | 30 | # Comment to post when marking as stale. Set to `false` to disable 31 | markComment: > 32 | This issue has been automatically marked as stale because it has not had 33 | recent activity. It will be closed if no further activity occurs. Thank you 34 | for your contributions. 35 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: master 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'doc/**' 7 | - '*.md' 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-java@v3 18 | with: 19 | distribution: 'temurin' 20 | java-version: '11' 21 | 22 | - name: run tests 23 | run: ./gradlew check 24 | 25 | - name: deploy snapshot 26 | run: ./gradlew publish 27 | 28 | - name: bundle the build report 29 | if: failure() 30 | run: find . -type d -name 'reports' | zip -@ -r build-reports.zip 31 | 32 | - name: upload the build report 33 | if: failure() 34 | uses: actions/upload-artifact@master 35 | with: 36 | name: error-report 37 | path: build-reports.zip 38 | 39 | env: 40 | GRADLE_OPTS: -Dorg.gradle.configureondemand=true -Dorg.gradle.parallel=false -Dorg.gradle.jvmargs="-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" 41 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 42 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 43 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} 44 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} 45 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - 'doc/**' 7 | - '*.md' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout the repo 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup JDK 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: '11' 20 | 21 | - name: Run tests 22 | run: ./gradlew check 23 | 24 | - name: Bundle the build report 25 | if: failure() 26 | run: find . -type d -name 'reports' | zip -@ -r build-reports.zip 27 | 28 | - name: Upload the build report 29 | if: failure() 30 | uses: actions/upload-artifact@master 31 | with: 32 | name: error-report 33 | path: build-reports.zip 34 | 35 | env: 36 | GRADLE_OPTS: -Dorg.gradle.configureondemand=true -Dorg.gradle.parallel=false -Dorg.gradle.jvmargs="-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "The release version" 8 | required: true 9 | branch: 10 | description: "The branch to release from" 11 | required: true 12 | default: 'master' 13 | 14 | jobs: 15 | publish: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout the repo 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | ref: ${{ github.event.inputs.branch }} 24 | 25 | - name: Setup JDK 26 | uses: actions/setup-java@v1 27 | with: 28 | java-version: '11' 29 | 30 | - name: deploy to sonatype 31 | run: ./gradlew publish 32 | 33 | - name: tag release 34 | run: | 35 | git tag v${{ github.event.inputs.version }} 36 | git push --tags 37 | 38 | env: 39 | GRADLE_OPTS: -Dorg.gradle.configureondemand=true -Dorg.gradle.parallel=false -Dorg.gradle.jvmargs="-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" 40 | RELEASE_VERSION: ${{ github.event.inputs.version }} 41 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 42 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 43 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} 44 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | .idea 4 | *.iml 5 | .kotlintest 6 | 7 | target/ 8 | build/ 9 | out/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | credentials.sbt 15 | .gradle -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.kotlin.dsl.`kotlin-dsl` 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | plugins { 8 | `kotlin-dsl` 9 | } 10 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Ci.kt: -------------------------------------------------------------------------------- 1 | object Ci { 2 | 3 | // this is the version used for building snapshots 4 | // .GITHUB_RUN_NUMBER-snapshot will be appended 5 | private const val snapshotBase = "3.0.0" 6 | 7 | private val githubRunNumber = System.getenv("GITHUB_RUN_NUMBER") 8 | 9 | private val snapshotVersion = when (githubRunNumber) { 10 | null -> "$snapshotBase-LOCAL" 11 | else -> "$snapshotBase.${githubRunNumber}-SNAPSHOT" 12 | } 13 | 14 | private val releaseVersion = System.getenv("RELEASE_VERSION") 15 | 16 | val isRelease = releaseVersion != null 17 | val version = releaseVersion ?: snapshotVersion 18 | } 19 | -------------------------------------------------------------------------------- /example-maven/src/main/kotlin/com/sksamuel/hoplite/example/Config.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.example 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import com.sksamuel.hoplite.Secret 5 | 6 | data class Config(val name: String, 7 | val env: String, 8 | val host: String, 9 | val port: Int, 10 | val user: String, 11 | val password: Secret) 12 | 13 | fun main() { 14 | val config = ConfigLoader().loadConfigOrThrow("/application.json") 15 | println(config) 16 | } 17 | -------------------------------------------------------------------------------- /example-maven/src/main/resources/application.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-app", 3 | "env": "staging", 4 | "host": "localhost", 5 | "port": 3306, 6 | "user": "mysql", 7 | "password": "letmein" 8 | } 9 | -------------------------------------------------------------------------------- /example-native/META-INF/native-image/generated/jni-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /example-native/META-INF/native-image/generated/predefined-classes-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type":"agent-extracted", 4 | "classes":[ 5 | ] 6 | } 7 | ] 8 | 9 | -------------------------------------------------------------------------------- /example-native/META-INF/native-image/generated/proxy-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /example-native/META-INF/native-image/generated/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources":{ 3 | "includes":[ 4 | { 5 | "pattern":"\\QMETA-INF/services/com.sksamuel.hoplite.decoder.Decoder\\E" 6 | }, 7 | { 8 | "pattern":"\\QMETA-INF/services/com.sksamuel.hoplite.parsers.Parser\\E" 9 | }, 10 | { 11 | "pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoader\\E" 12 | }, 13 | { 14 | "pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition\\E" 15 | } 16 | ]}, 17 | "bundles":[] 18 | } 19 | -------------------------------------------------------------------------------- /example-native/META-INF/native-image/generated/serialization-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /example-native/META-INF/native-image/kotlin-resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources": [ 3 | {"pattern": "META-INF/.*.kotlin_module$"}, 4 | {"pattern": "META-INF/services/.*"}, 5 | {"pattern": ".*.kotlin_builtins"} 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /example-native/README.md: -------------------------------------------------------------------------------- 1 | # Hoplite and GraalVM native image example 2 | 3 | ## Prerequisites 4 | 5 | You need a GraalVM java distribution with installed native image compiler: 6 | 7 | ```shell 8 | gu install native-image 9 | ``` 10 | 11 | See also: 12 | - [Install GraalVM](https://www.graalvm.org/22.0/docs/getting-started/#install-graalvm) 13 | - [Native image support](https://www.graalvm.org/22.0/reference-manual/native-image/#install-native-image) 14 | 15 | ## Updating GraalVM native image configs 16 | 17 | ```shell 18 | ../gradlew run -Dnative.image.agent=1 --args="config.yaml" 19 | ``` 20 | 21 | ## Building native executable 22 | 23 | ```shell 24 | ../gradlew clean nativeBuild 25 | ``` 26 | 27 | A key config that you need is [kotlin-resource.json](https://github.com/sksamuel/hoplite/tree/master/example-native/META-INF/native-image/kotlin-resource.json) 28 | It should be added into `-H:ResourceConfigurationFiles` native build option, see `build.gradle.kts` for details. 29 | 30 | ## Running native executable 31 | 32 | ```shell 33 | ./build/native/nativeBuild/example config.yaml 34 | ``` 35 | -------------------------------------------------------------------------------- /example-native/config.yaml: -------------------------------------------------------------------------------- 1 | name: Hello 2 | nested: 3 | nums: [1, 2, 3] 4 | -------------------------------------------------------------------------------- /example-native/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | includeBuild("..") 2 | -------------------------------------------------------------------------------- /example-native/src/main/kotlin/com/sksamuel/hoplite/example/Main.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.example 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | 5 | import java.nio.file.Path 6 | 7 | data class MyConfig( 8 | val name: String, 9 | val nested: NestedConfig 10 | ) 11 | 12 | data class NestedConfig( 13 | val nums: List 14 | ) 15 | 16 | fun main(args: Array) { 17 | val configPath = Path.of(args[0]) 18 | val config = ConfigLoader().loadConfigOrThrow(configPath) 19 | println("My config is: $config") 20 | } 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #https://github.com/gradle/gradle/issues/11308 2 | org.gradle.internal.publish.checksums.insecure=true 3 | systemProp.org.gradle.internal.publish.checksums.insecure=true 4 | org.gradle.parallel=false 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sksamuel/hoplite/e211e07c369b40a7dc9a706f1f0d144a9c457266/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /hoplite-arrow/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | testImplementation(projects.hopliteToml) 4 | testImplementation(projects.hopliteYaml) 5 | testImplementation(projects.hopliteJson) 6 | implementation(libs.arrow.core) 7 | } 8 | 9 | apply("../publish.gradle.kts") 10 | -------------------------------------------------------------------------------- /hoplite-arrow/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.decoder.arrow.OptionDecoder 2 | com.sksamuel.hoplite.decoder.arrow.NonEmptyListDecoder 3 | com.sksamuel.hoplite.decoder.arrow.Tuple2Decoder 4 | com.sksamuel.hoplite.decoder.arrow.Tuple3Decoder 5 | com.sksamuel.hoplite.decoder.arrow.Tuple4Decoder 6 | com.sksamuel.hoplite.decoder.arrow.Tuple5Decoder 7 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/kotlin/com/sksamuel/hoplite/arrow/NonEmptyListTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.arrow 2 | 3 | import arrow.core.nonEmptyListOf 4 | import com.sksamuel.hoplite.ConfigLoader 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class NonEmptyListTest : FunSpec({ 9 | 10 | test("NonEmptyList as delimited string") { 11 | data class Test(val strings: arrow.core.NonEmptyList, val longs: arrow.core.NonEmptyList) 12 | 13 | val config = ConfigLoader().loadConfigOrThrow("/test_nel.yml") 14 | config shouldBe Test(nonEmptyListOf("1", "2", "a", "b"), nonEmptyListOf(1L, 2L, 3L, 4L)) 15 | } 16 | 17 | }) 18 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/kotlin/com/sksamuel/hoplite/arrow/OptionTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.arrow 2 | 3 | import arrow.core.None 4 | import arrow.core.Option 5 | import arrow.core.Some 6 | import com.sksamuel.hoplite.ConfigLoader 7 | import io.kotest.core.spec.style.FunSpec 8 | import io.kotest.matchers.shouldBe 9 | import java.util.* 10 | 11 | class OptionTest : FunSpec({ 12 | 13 | test("support Options") { 14 | data class Foo(val a: Option, 15 | val b: Option, 16 | val c: Option, 17 | val d: Option, 18 | val e: Option) 19 | 20 | val foo = ConfigLoader().loadConfigOrThrow("/options.yml") 21 | foo.a shouldBe None 22 | foo.b shouldBe None 23 | foo.c shouldBe Some("hello") 24 | foo.d shouldBe Some(123L) 25 | foo.e shouldBe Some(UUID.fromString("383d27c5-d087-4d36-b4c4-6dd7defe088d")) 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/resources/options.yml: -------------------------------------------------------------------------------- 1 | a: null 2 | b: null 3 | c: "hello" 4 | d: 123 5 | e: "383d27c5-d087-4d36-b4c4-6dd7defe088d" 6 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/resources/test_nel.yml: -------------------------------------------------------------------------------- 1 | strings: 1,2,a,b 2 | longs: 1,2,3,4 3 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/resources/test_tuple_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": [ 3 | 6.5, 4 | true, 5 | "383d27c5-d087-4d36-b4c4-6dd7defe088d", 6 | 9200 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/resources/test_tuple_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": [ 3 | 6.5, 4 | true, 5 | "383d27c5-d087-4d36-b4c4-6dd7defe088d", 6 | 9200, 7 | "hello" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/resources/test_tuples.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": [ 3 | "hello", 4 | 4 5 | ], 6 | "b": [ 7 | 6.5, 8 | true, 9 | "383d27c5-d087-4d36-b4c4-6dd7defe088d" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/resources/test_tuples.toml: -------------------------------------------------------------------------------- 1 | a = ["hello", "4"] 2 | b = ["6.5", "true", "383d27c5-d087-4d36-b4c4-6dd7defe088d"] 3 | -------------------------------------------------------------------------------- /hoplite-arrow/src/test/resources/test_tuples.yml: -------------------------------------------------------------------------------- 1 | a: [ "hello", 4 ] 2 | b: [ 6.5, true, "383d27c5-d087-4d36-b4c4-6dd7defe088d" ] 3 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.serialization) 3 | } 4 | 5 | dependencies { 6 | api(projects.hopliteCore) 7 | api(libs.aws.kotlin.secretsmanager) 8 | api(libs.aws.kotlin.ssm) 9 | api(libs.regions) 10 | implementation(libs.kotlinx.serialization.json) 11 | testApi(libs.kotest.extensions.testcontainers) 12 | testApi(libs.testcontainers.localstack) 13 | } 14 | 15 | apply("../publish.gradle.kts") 16 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/main/kotlin/com/sksamuel/hoplite/aws/kotlin/AbstractAwsSecretsManagerContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws.kotlin 2 | 3 | import aws.sdk.kotlin.services.secretsmanager.SecretsManagerClient 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.flatMap 9 | import com.sksamuel.hoplite.resolver.context.ContextResolver 10 | import kotlinx.coroutines.runBlocking 11 | 12 | abstract class AbstractAwsSecretsManagerContextResolver( 13 | private val report: Boolean = false, 14 | createClient: () -> SecretsManagerClient = { runBlocking { SecretsManagerClient.fromEnvironment() } } 15 | ) : ContextResolver() { 16 | 17 | // should stay lazy so still be added to config even when not used, eg locally 18 | private val client by lazy { createClient() } 19 | private val ops by lazy { AwsOps(client) } 20 | 21 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 22 | val (key, index) = ops.extractIndex(path) 23 | return ops.fetchSecret(key) 24 | .onSuccess { if (report) ops.report(context, it) } 25 | .flatMap { ops.parseSecret(it, index) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/main/kotlin/com/sksamuel/hoplite/aws/kotlin/AwsSecretsManagerContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws.kotlin 2 | 3 | import aws.sdk.kotlin.services.secretsmanager.SecretsManagerClient 4 | import kotlinx.coroutines.runBlocking 5 | 6 | /** 7 | * Replaces strings of the form ${{ aws-secrets-manager:path }} by looking up the path in AWS Secrets Manager. 8 | * 9 | * The [SecretsManagerClient] client is created from the [createClient] argument which uses the 10 | * standard [SecretsManagerClient.fromEnvironment()] by default. 11 | */ 12 | class AwsSecretsManagerContextResolver( 13 | report: Boolean = false, 14 | createClient: () -> SecretsManagerClient = { runBlocking { SecretsManagerClient.fromEnvironment() } } 15 | ) : AbstractAwsSecretsManagerContextResolver(report, createClient) { 16 | override val contextKey: String = "aws-secrets-manager" 17 | override val default: Boolean = false 18 | } 19 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/main/kotlin/com/sksamuel/hoplite/aws/kotlin/RegionDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws.kotlin 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.decoder.NullHandlingDecoder 9 | import com.sksamuel.hoplite.decoder.toValidated 10 | import com.sksamuel.hoplite.fp.invalid 11 | import software.amazon.awssdk.regions.Region 12 | import java.util.Locale 13 | import kotlin.reflect.KType 14 | 15 | class RegionDecoder : NullHandlingDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == Region::class 18 | 19 | override fun safeDecode( 20 | node: Node, 21 | type: KType, 22 | context: DecoderContext 23 | ): ConfigResult { 24 | fun regionFromName(name: String): ConfigResult = 25 | runCatching { 26 | Region.regions().single { it.id() == name.lowercase(Locale.getDefault()).replace("_", "-") } 27 | }.toValidated { ConfigFailure.Generic("Cannot create region from $name") } 28 | 29 | return when (node) { 30 | is StringNode -> regionFromName(node.value) 31 | else -> ConfigFailure.DecodeError(node, type).invalid() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.aws.kotlin.RegionDecoder 2 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | ${LOG_PATTERN} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/test/resources/multiple_secrets.props: -------------------------------------------------------------------------------- 1 | a=secretsmanager://foo.bar 2 | b=secretsmanager://bar.baz 3 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/test/resources/secrets.props: -------------------------------------------------------------------------------- 1 | a=awssm://foo 2 | -------------------------------------------------------------------------------- /hoplite-aws-kotlin/src/test/resources/ssm.props: -------------------------------------------------------------------------------- 1 | a=${ssm:/foo/boo} 2 | -------------------------------------------------------------------------------- /hoplite-aws/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.serialization) 3 | } 4 | 5 | dependencies { 6 | api(projects.hopliteCore) 7 | api(libs.aws.java.sdk.secretsmanager) 8 | api(libs.aws.java.sdk.ssm) 9 | implementation(libs.kotlinx.serialization.json) 10 | testApi(libs.kotest.extensions.testcontainers) 11 | testApi(libs.testcontainers.localstack) 12 | } 13 | 14 | apply("../publish.gradle.kts") 15 | -------------------------------------------------------------------------------- /hoplite-aws/src/main/kotlin/com/sksamuel/hoplite/aws/AbstractAwsSecretsManagerContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws 2 | 3 | import com.amazonaws.services.secretsmanager.AWSSecretsManager 4 | import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder 5 | import com.sksamuel.hoplite.ConfigResult 6 | import com.sksamuel.hoplite.DecoderContext 7 | import com.sksamuel.hoplite.Node 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.fp.flatMap 10 | import com.sksamuel.hoplite.resolver.context.ContextResolver 11 | 12 | abstract class AbstractAwsSecretsManagerContextResolver( 13 | private val report: Boolean = false, 14 | createClient: () -> AWSSecretsManager = { AWSSecretsManagerClientBuilder.standard().build() } 15 | ) : ContextResolver() { 16 | 17 | // should stay lazy so still be added to config even when not used, eg locally 18 | private val client by lazy { createClient() } 19 | private val ops by lazy { AwsOps(client) } 20 | 21 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 22 | val (key, index) = ops.extractIndex(path) 23 | context.reportPrintFn("[WARN] Use of the hoplite-aws module is deprecated. Please use the hoplite-aws2 module instead.") 24 | return ops.fetchSecret(key) 25 | .onSuccess { if (report) ops.report(context, it) } 26 | .flatMap { ops.parseSecret(it, index) } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hoplite-aws/src/main/kotlin/com/sksamuel/hoplite/aws/AwsSecretsManagerContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws 2 | 3 | import com.amazonaws.services.secretsmanager.AWSSecretsManager 4 | import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder 5 | 6 | /** 7 | * Replaces strings of the form ${{ aws-secrets-manager:path }} by looking up the path in AWS Secrets Manager. 8 | * 9 | * The [AWSSecretsManager] client is created from the [createClient] argument which uses the 10 | * standard [AWSSecretsManagerClientBuilder] by default. 11 | */ 12 | class AwsSecretsManagerContextResolver( 13 | report: Boolean = false, 14 | createClient: () -> AWSSecretsManager = { AWSSecretsManagerClientBuilder.standard().build() } 15 | ) : AbstractAwsSecretsManagerContextResolver(report, createClient) { 16 | override val contextKey: String = "aws-secrets-manager" 17 | override val default: Boolean = false 18 | } 19 | -------------------------------------------------------------------------------- /hoplite-aws/src/main/kotlin/com/sksamuel/hoplite/aws/RegionDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws 2 | 3 | import com.amazonaws.regions.Region 4 | import com.amazonaws.regions.Regions 5 | import com.sksamuel.hoplite.ConfigFailure 6 | import com.sksamuel.hoplite.ConfigResult 7 | import com.sksamuel.hoplite.DecoderContext 8 | import com.sksamuel.hoplite.Node 9 | import com.sksamuel.hoplite.StringNode 10 | import com.sksamuel.hoplite.decoder.NullHandlingDecoder 11 | import com.sksamuel.hoplite.decoder.toValidated 12 | import com.sksamuel.hoplite.fp.invalid 13 | import kotlin.reflect.KType 14 | 15 | class RegionDecoder : NullHandlingDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == Region::class 18 | 19 | override fun safeDecode(node: Node, 20 | type: KType, 21 | context: DecoderContext): ConfigResult { 22 | 23 | fun regionFromName(name: String): ConfigResult { 24 | context.reportPrintFn("[WARN] Use of the hoplite-aws module is deprecated. Please use the hoplite-aws2 module instead.") 25 | return runCatching { Region.getRegion(Regions.fromName(name)) } 26 | .toValidated { ConfigFailure.Generic("Cannot create region from $name") } 27 | } 28 | 29 | return when (node) { 30 | is StringNode -> regionFromName(node.value) 31 | else -> ConfigFailure.DecodeError(node, type).invalid() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hoplite-aws/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.aws.RegionDecoder -------------------------------------------------------------------------------- /hoplite-aws/src/test/kotlin/com/sksamuel/hoplite/aws/RegionDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws 2 | 3 | import com.amazonaws.regions.Region 4 | import com.amazonaws.regions.Regions 5 | import com.sksamuel.hoplite.ConfigFailure 6 | import com.sksamuel.hoplite.DecoderContext 7 | import com.sksamuel.hoplite.Pos 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.decoder.DotPath 10 | import com.sksamuel.hoplite.fp.invalid 11 | import com.sksamuel.hoplite.fp.valid 12 | import io.kotest.core.spec.style.StringSpec 13 | import io.kotest.matchers.shouldBe 14 | import kotlin.reflect.full.createType 15 | 16 | class RegionDecoderTest : StringSpec() { 17 | init { 18 | "region converter" { 19 | RegionDecoder().safeDecode( 20 | StringNode("us-east-1", Pos.NoPos, DotPath.root, emptyMap()), 21 | Region::class.createType(), 22 | DecoderContext.zero 23 | ) shouldBe Region.getRegion(Regions.US_EAST_1).valid() 24 | 25 | RegionDecoder().safeDecode( 26 | StringNode("us-qwewqe-1", Pos.NoPos, DotPath.root, emptyMap()), 27 | Region::class.createType(), 28 | DecoderContext.zero 29 | ) shouldBe 30 | ConfigFailure.Generic("Cannot create region from us-qwewqe-1").invalid() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hoplite-aws/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | ${LOG_PATTERN} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hoplite-aws/src/test/resources/multiple_secrets.props: -------------------------------------------------------------------------------- 1 | a=secretsmanager://foo.bar 2 | b=secretsmanager://bar.baz 3 | -------------------------------------------------------------------------------- /hoplite-aws/src/test/resources/secrets.props: -------------------------------------------------------------------------------- 1 | a=awssm://foo 2 | -------------------------------------------------------------------------------- /hoplite-aws/src/test/resources/ssm.props: -------------------------------------------------------------------------------- 1 | a=${ssm:/foo/boo} 2 | -------------------------------------------------------------------------------- /hoplite-aws2/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.serialization) 3 | } 4 | 5 | dependencies { 6 | api(projects.hopliteCore) 7 | api(libs.regions) 8 | api(libs.secretsmanager) 9 | implementation(libs.kotlinx.serialization.json) 10 | testApi("io.kotest.extensions:kotest-extensions-testcontainers:2.0.2") 11 | testApi(libs.testcontainers.localstack) 12 | } 13 | 14 | apply("../publish.gradle.kts") 15 | -------------------------------------------------------------------------------- /hoplite-aws2/src/main/kotlin/com/sksamuel/hoplite/aws2/AwsSecretsManagerSource.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws2 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import kotlinx.serialization.json.Json 5 | import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient 6 | import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest 7 | 8 | /** 9 | * Fetches a secret from AWS Secrets manager, which can be added as a 10 | * map source for configuration 11 | */ 12 | class AwsSecretsManagerSource( 13 | private val json: Json = Json.Default, 14 | private val createClient: () -> SecretsManagerClient = { SecretsManagerClient.create() }, 15 | ) { 16 | 17 | private val client by lazy { createClient() } 18 | 19 | fun fetchSecretAsMap(key: String): Map { 20 | val secretRequest = GetSecretValueRequest.builder().secretId(key).build() 21 | val secret = client.getSecretValue(secretRequest) 22 | 23 | return json.decodeFromString>(secret.secretString()) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /hoplite-aws2/src/main/kotlin/com/sksamuel/hoplite/aws2/RegionDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.aws2 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.decoder.NullHandlingDecoder 9 | import com.sksamuel.hoplite.decoder.toValidated 10 | import com.sksamuel.hoplite.fp.invalid 11 | import software.amazon.awssdk.regions.Region 12 | import java.util.Locale 13 | import kotlin.reflect.KType 14 | 15 | class RegionDecoder : NullHandlingDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == Region::class 18 | 19 | override fun safeDecode( 20 | node: Node, 21 | type: KType, 22 | context: DecoderContext 23 | ): ConfigResult { 24 | fun regionFromName(name: String): ConfigResult = 25 | runCatching { 26 | Region.regions().single { it.id() == name.lowercase(Locale.getDefault()).replace("_", "-") } 27 | }.toValidated { ConfigFailure.Generic("Cannot create region from $name") } 28 | 29 | return when (node) { 30 | is StringNode -> regionFromName(node.value) 31 | else -> ConfigFailure.DecodeError(node, type).invalid() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hoplite-aws2/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.aws2.RegionDecoder 2 | -------------------------------------------------------------------------------- /hoplite-azure/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | api(libs.azure.security.keyvault.secrets) 4 | api(libs.azure.identity) 5 | testApi(libs.kotest.extensions.testcontainers) 6 | } 7 | 8 | apply("../publish.gradle.kts") 9 | -------------------------------------------------------------------------------- /hoplite-azure/src/main/kotlin/com/sksamuel/hoplite/azure/AzureKeyVaultContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.azure 2 | 3 | import com.azure.identity.DefaultAzureCredentialBuilder 4 | import com.azure.security.keyvault.secrets.SecretClient 5 | import com.azure.security.keyvault.secrets.SecretClientBuilder 6 | import com.sksamuel.hoplite.ConfigResult 7 | import com.sksamuel.hoplite.DecoderContext 8 | import com.sksamuel.hoplite.Node 9 | import com.sksamuel.hoplite.StringNode 10 | import com.sksamuel.hoplite.resolver.context.ContextResolver 11 | 12 | class AzureKeyVaultContextResolver( 13 | private val report: Boolean = false, 14 | private val createClient: () -> SecretClient 15 | ) : ContextResolver() { 16 | 17 | constructor(url: String) : this(url, false) 18 | constructor(url: String, report: Boolean) : this(report = report, { 19 | SecretClientBuilder() 20 | .vaultUrl(url) 21 | .credential(DefaultAzureCredentialBuilder().build()) 22 | .buildClient() 23 | }) 24 | 25 | private val client = lazy { createClient() } 26 | private val ops = lazy { AzureOps(client.value) } 27 | 28 | override val contextKey = "azure-key-vault" 29 | override val default: Boolean = true 30 | 31 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 32 | return ops.value.fetchSecret(path, context, report) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hoplite-consul/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | api(libs.consul.client) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(libs.kotlin.reflect) 3 | testImplementation(libs.postgresql) 4 | api(libs.coroutines.core) 5 | api(libs.coroutines.jdk8) 6 | testImplementation(libs.kotest.extensions.testcontainers) 7 | testImplementation(libs.testcontainers) 8 | testImplementation(libs.testcontainers.postgresql) 9 | } 10 | 11 | apply("../publish.gradle.kts") 12 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | object Constants { 4 | const val indent = " " 5 | } 6 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/Pos.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | /** 4 | * Tracks where in a file a config value was retrieved. 5 | */ 6 | sealed class Pos { 7 | 8 | /** 9 | * Used if no positional information was present. 10 | */ 11 | object NoPos : Pos() 12 | 13 | /** 14 | * Used when the only information available is a source name, such as env-vars or input streams. 15 | */ 16 | data class SourcePos(val source: String) : Pos() 17 | 18 | /** 19 | * Used when we know the filename and the line. 20 | */ 21 | data class LinePos(val line: Int, val source: String) : Pos() 22 | 23 | /** 24 | * Used when we know the filename, line and columnn. 25 | */ 26 | data class LineColPos(val line: Int, val col: Int, val source: String) : Pos() 27 | 28 | fun source(): String? = when (this) { 29 | is SourcePos -> this.source 30 | is LineColPos -> this.source 31 | is LinePos -> this.source 32 | NoPos -> null 33 | } 34 | 35 | companion object { 36 | val env = SourcePos("env") 37 | val sysprops = SourcePos("sysprops") 38 | } 39 | } 40 | 41 | fun Pos.loc() = when (this) { 42 | is Pos.NoPos -> "" 43 | is Pos.SourcePos -> "($source)" 44 | is Pos.LineColPos -> "($source:$line:$col)" 45 | is Pos.LinePos -> "($source:$line)" 46 | } 47 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/Reporter.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | /** 4 | * Allows [Resolver]s and [Decoder]s to add content to the output report. 5 | */ 6 | class Reporter { 7 | 8 | private val sections: MutableMap>> = mutableMapOf() 9 | 10 | fun getReport(): Map>> = sections.toMap() 11 | 12 | /** 13 | * Adds a [row] to the named [section] in the report. 14 | */ 15 | fun report(section: String, row: Map) { 16 | val rows = sections.getOrPut(section) { emptyList() } 17 | sections[section] = (rows + row).distinct() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/Base64Decoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.invalid 9 | import com.sksamuel.hoplite.fp.valid 10 | import java.nio.ByteBuffer 11 | import kotlin.reflect.KType 12 | 13 | data class Base64(val value: ByteBuffer) 14 | 15 | class Base64Decoder : Decoder { 16 | override fun supports(type: KType): Boolean = type.classifier == Base64::class 17 | override fun decode(node: Node, type: KType, context: DecoderContext): ConfigResult { 18 | return when (node) { 19 | is StringNode -> runCatching { 20 | java.util.Base64.getDecoder().decode(node.value) 21 | }.fold( 22 | { Base64(ByteBuffer.wrap(it)).valid() }, 23 | { ConfigFailure.DecodeError(node, type).invalid() }) 24 | else -> ConfigFailure.DecodeError(node, type).invalid() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/BigDecimalDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.fp.valid 5 | import com.sksamuel.hoplite.ConfigFailure 6 | import com.sksamuel.hoplite.ConfigResult 7 | import com.sksamuel.hoplite.DecoderContext 8 | import com.sksamuel.hoplite.DoubleNode 9 | import com.sksamuel.hoplite.LongNode 10 | import com.sksamuel.hoplite.StringNode 11 | import com.sksamuel.hoplite.Node 12 | import com.sksamuel.hoplite.ThrowableFailure 13 | import java.math.BigDecimal 14 | import kotlin.reflect.KType 15 | 16 | class BigDecimalDecoder : NonNullableLeafDecoder { 17 | override fun supports(type: KType): Boolean = type.classifier == BigDecimal::class 18 | override fun safeLeafDecode(node: Node, 19 | type: KType, 20 | context: DecoderContext): ConfigResult = when (node) { 21 | is StringNode -> runCatching { node.value.toDouble().toBigDecimal() }.toValidated { ThrowableFailure(it) } 22 | is LongNode -> node.value.toBigDecimal().valid() 23 | is DoubleNode -> node.value.toBigDecimal().valid() 24 | else -> ConfigFailure.DecodeError(node, type).invalid() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/BigIntegerDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.fp.valid 5 | import com.sksamuel.hoplite.ConfigFailure 6 | import com.sksamuel.hoplite.ConfigResult 7 | import com.sksamuel.hoplite.DecoderContext 8 | import com.sksamuel.hoplite.LongNode 9 | import com.sksamuel.hoplite.StringNode 10 | import com.sksamuel.hoplite.Node 11 | import com.sksamuel.hoplite.ThrowableFailure 12 | import java.math.BigInteger 13 | import kotlin.reflect.KType 14 | 15 | class BigIntegerDecoder : NonNullableLeafDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == BigInteger::class 18 | override fun safeLeafDecode(node: Node, 19 | type: KType, 20 | context: DecoderContext): ConfigResult = when (node) { 21 | is StringNode -> runCatching { node.value.toLong().toBigInteger() }.toValidated { ThrowableFailure(it) } 22 | is LongNode -> node.value.toBigInteger().valid() 23 | else -> ConfigFailure.DecodeError(node, type).invalid() 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/BooleanArrayDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.invalid 9 | import com.sksamuel.hoplite.fp.valid 10 | import kotlin.reflect.KType 11 | 12 | class BooleanArrayDecoder : Decoder { 13 | override fun supports(type: KType): Boolean = type.classifier == BooleanArray::class 14 | override fun decode(node: Node, type: KType, context: DecoderContext): ConfigResult { 15 | return when (node) { 16 | is StringNode -> node.value.split(",").map { it.trim().toBooleanStrict() }.toBooleanArray().valid() 17 | else -> ConfigFailure.DecodeError(node, type).invalid() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/ByteArrayDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.invalid 9 | import com.sksamuel.hoplite.fp.valid 10 | import kotlin.reflect.KType 11 | 12 | class ByteArrayDecoder : Decoder { 13 | override fun supports(type: KType): Boolean = type.classifier == ByteArray::class 14 | override fun decode(node: Node, type: KType, context: DecoderContext): ConfigResult { 15 | return when (node) { 16 | is StringNode -> node.value.encodeToByteArray().valid() 17 | else -> ConfigFailure.DecodeError(node, type).invalid() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/InetAddressDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.ConfigFailure 5 | import com.sksamuel.hoplite.ConfigResult 6 | import com.sksamuel.hoplite.DecoderContext 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.Node 9 | import com.sksamuel.hoplite.ThrowableFailure 10 | import java.net.InetAddress 11 | import kotlin.reflect.KType 12 | 13 | class InetAddressDecoder : NullHandlingDecoder { 14 | override fun supports(type: KType): Boolean = type.classifier == InetAddress::class 15 | override fun safeDecode(node: Node, 16 | type: KType, 17 | context: DecoderContext): ConfigResult = when (node) { 18 | is StringNode -> runCatching { InetAddress.getByName(node.value) }.toValidated { ThrowableFailure(it) } 19 | else -> ConfigFailure.DecodeError(node, type).invalid() 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/KClassDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.ConfigFailure 5 | import com.sksamuel.hoplite.ConfigResult 6 | import com.sksamuel.hoplite.DecoderContext 7 | import com.sksamuel.hoplite.Node 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.ThrowableFailure 10 | import kotlin.reflect.KClass 11 | import kotlin.reflect.KType 12 | 13 | class KClassDecoder : NullHandlingDecoder> { 14 | override fun supports(type: KType): Boolean = type.classifier == KClass::class 15 | override fun safeDecode(node: Node, 16 | type: KType, 17 | context: DecoderContext): ConfigResult> = when (node) { 18 | is StringNode -> kotlin.runCatching { Class.forName(node.value).kotlin }.toValidated { ThrowableFailure(it) } 19 | else -> ConfigFailure.DecodeError(node, type).invalid() 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/LocaleDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.invalid 9 | import com.sksamuel.hoplite.fp.valid 10 | import java.util.Locale 11 | import kotlin.reflect.KType 12 | 13 | class LocaleDecoder : NullHandlingDecoder { 14 | 15 | override fun supports(type: KType): Boolean = type.classifier == Locale::class 16 | 17 | override fun safeDecode( 18 | node: Node, 19 | type: KType, 20 | context: DecoderContext 21 | ): ConfigResult = when (node) { 22 | is StringNode -> Locale.forLanguageTag(node.value).valid() 23 | else -> ConfigFailure.DecodeError(node, type).invalid() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/MinutesDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.LongNode 7 | import com.sksamuel.hoplite.Node 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.ThrowableFailure 10 | import com.sksamuel.hoplite.fp.invalid 11 | import com.sksamuel.hoplite.fp.valid 12 | import kotlin.reflect.KType 13 | import kotlin.time.Duration.Companion.minutes 14 | 15 | class MinutesDecoder : NonNullableLeafDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == Minutes::class 18 | override fun safeLeafDecode( 19 | node: Node, 20 | type: KType, 21 | context: DecoderContext 22 | ): ConfigResult = when (node) { 23 | is StringNode -> runCatching { Minutes(node.value.toLong()) }.toValidated { ThrowableFailure(it) } 24 | is LongNode -> Minutes(node.value).valid() 25 | else -> ConfigFailure.DecodeError(node, type).invalid() 26 | } 27 | } 28 | 29 | data class Minutes(val value: Long) 30 | 31 | fun Minutes.duration() = value.minutes 32 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/PrincipalDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.fp.valid 5 | import com.sksamuel.hoplite.ConfigFailure 6 | import com.sksamuel.hoplite.ConfigResult 7 | import com.sksamuel.hoplite.DecoderContext 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.Node 10 | import java.security.Principal 11 | import kotlin.reflect.KType 12 | 13 | class PrincipalDecoder : NonNullableLeafDecoder { 14 | override fun supports(type: KType): Boolean = type.classifier == Principal::class 15 | override fun safeLeafDecode(node: Node, 16 | type: KType, 17 | context: DecoderContext): ConfigResult = when (node) { 18 | is StringNode -> BasicPrincipal(node.value).valid() 19 | else -> ConfigFailure.DecodeError(node, type).invalid() 20 | } 21 | } 22 | 23 | data class BasicPrincipal(private val name: String) : Principal { 24 | override fun getName(): String = name 25 | } 26 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/RegexDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.fp.valid 5 | import com.sksamuel.hoplite.ConfigFailure 6 | import com.sksamuel.hoplite.ConfigResult 7 | import com.sksamuel.hoplite.DecoderContext 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.Node 10 | import kotlin.reflect.KType 11 | 12 | class RegexDecoder : NullHandlingDecoder { 13 | 14 | override fun supports(type: KType): Boolean = type.classifier == Regex::class 15 | 16 | override fun safeDecode(node: Node, 17 | type: KType, 18 | context: DecoderContext): ConfigResult = when (node) { 19 | is StringNode -> node.value.toRegex().valid() 20 | else -> ConfigFailure.DecodeError(node, type).invalid() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/SecondsDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.LongNode 7 | import com.sksamuel.hoplite.Node 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.ThrowableFailure 10 | import com.sksamuel.hoplite.fp.invalid 11 | import com.sksamuel.hoplite.fp.valid 12 | import kotlin.reflect.KType 13 | import kotlin.time.Duration.Companion.seconds 14 | 15 | class SecondsDecoder : NonNullableLeafDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == Seconds::class 18 | override fun safeLeafDecode( 19 | node: Node, 20 | type: KType, 21 | context: DecoderContext 22 | ): ConfigResult = when (node) { 23 | is StringNode -> runCatching { Seconds(node.value.toLong()) }.toValidated { ThrowableFailure(it) } 24 | is LongNode -> Seconds(node.value).valid() 25 | else -> ConfigFailure.DecodeError(node, type).invalid() 26 | } 27 | } 28 | 29 | data class Seconds(val value: Long) 30 | 31 | fun Seconds.duration() = value.seconds 32 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/SecretDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.DoubleNode 7 | import com.sksamuel.hoplite.LongNode 8 | import com.sksamuel.hoplite.Node 9 | import com.sksamuel.hoplite.Secret 10 | import com.sksamuel.hoplite.StringNode 11 | import com.sksamuel.hoplite.fp.invalid 12 | import com.sksamuel.hoplite.fp.valid 13 | import kotlin.reflect.KType 14 | 15 | class SecretDecoder : NullHandlingDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == Secret::class 18 | 19 | override fun safeDecode( 20 | node: Node, 21 | type: KType, 22 | context: DecoderContext 23 | ): ConfigResult { 24 | return when (node) { 25 | is StringNode -> Secret(node.value).valid() 26 | is LongNode -> Secret(node.value.toString()).valid() 27 | is DoubleNode -> Secret(node.value.toString()).valid() 28 | else -> ConfigFailure.DecodeError(node, type).invalid() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/metric.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | object MetricSystem { 4 | const val Yocto = 1e-24 5 | const val Zepto = 1e-21 6 | const val Atto = 1e-18 7 | const val Femto = 1e-15 8 | const val Pico = 1e-12 9 | const val Nano = 1e-9 10 | const val Micro = 1e-6 11 | const val Milli = 1e-3 12 | const val Centi = 1e-2 13 | const val Deci = 1e-1 14 | 15 | const val Deca = 1e1 16 | const val Hecto = 1e2 17 | const val Kilo = 1e3 18 | const val Mega = 1e6 19 | const val Giga = 1e9 20 | const val Tera = 1e12 21 | const val Peta = 1e15 22 | const val Exa = 1e18 23 | const val Zetta = 1e21 24 | const val Yotta = 1e24 25 | } 26 | 27 | object BinarySystem { 28 | const val Kilo = 1024.0 29 | const val Mega = 1024.0 * Kilo 30 | const val Giga = 1024.0 * Mega 31 | const val Tera = 1024.0 * Giga 32 | const val Peta = 1024.0 * Tera 33 | const val Exa = 1024.0 * Peta 34 | const val Zetta = 1024.0 * Exa 35 | const val Yotta = 1024.0 * Zetta 36 | } 37 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/urls.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.ConfigFailure 5 | import com.sksamuel.hoplite.ConfigResult 6 | import com.sksamuel.hoplite.DecoderContext 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.Node 9 | import java.net.URI 10 | import java.net.URL 11 | import kotlin.reflect.KType 12 | 13 | class URLDecoder : NullHandlingDecoder { 14 | override fun supports(type: KType): Boolean = type.classifier == URL::class 15 | override fun safeDecode(node: Node, 16 | type: KType, 17 | context: DecoderContext): ConfigResult = when (node) { 18 | is StringNode -> runCatching { URL(node.value) }.toValidated { ConfigFailure.DecodeError(node, type) } 19 | else -> ConfigFailure.DecodeError(node, type).invalid() 20 | } 21 | } 22 | 23 | class URIDecoder : NullHandlingDecoder { 24 | override fun supports(type: KType): Boolean = type.classifier == URI::class 25 | override fun safeDecode(node: Node, 26 | type: KType, 27 | context: DecoderContext): ConfigResult = when (node) { 28 | is StringNode -> runCatching { URI.create(node.value) }.toValidated { ConfigFailure.DecodeError(node, type) } 29 | else -> ConfigFailure.DecodeError(node, type).invalid() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/decoder/uuid.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.fp.invalid 4 | import com.sksamuel.hoplite.ConfigFailure 5 | import com.sksamuel.hoplite.ConfigResult 6 | import com.sksamuel.hoplite.DecoderContext 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.Node 9 | import java.util.* 10 | import kotlin.reflect.KType 11 | 12 | class UUIDDecoder : NullHandlingDecoder { 13 | override fun supports(type: KType): Boolean = type.classifier == UUID::class 14 | override fun safeDecode(node: Node, 15 | type: KType, 16 | context: DecoderContext): ConfigResult = when (node) { 17 | is StringNode -> 18 | runCatching { UUID.fromString(node.value) } 19 | .toValidated { ConfigFailure.DecodeError(node, type) } 20 | else -> ConfigFailure.DecodeError(node, type).invalid() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/experimental.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | @RequiresOptIn 4 | @Retention(AnnotationRetention.BINARY) 5 | annotation class ExperimentalHoplite 6 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/fp.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import com.sksamuel.hoplite.fp.Validated 4 | 5 | typealias ConfigResult = Validated 6 | 7 | internal fun Result.flatMap(f: (T) -> Result) = fold({ f(it) }, { Result.failure(it) }) 8 | 9 | internal fun List>.sequence(): Result> { 10 | val ss = this.map { result -> result.getOrElse { return Result.failure(it) } } 11 | return Result.success(ss) 12 | } 13 | 14 | internal fun Result.toValidated(ifFailure: (Throwable) -> E): Validated = 15 | fold({ Validated.Valid(it) }, { Validated.Invalid(ifFailure(it)) }) 16 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/fp/NonEmptyList.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.fp 2 | 3 | class NonEmptyList(val list: List) { 4 | 5 | init { 6 | require(list.isNotEmpty()) 7 | } 8 | 9 | fun map(f: (A) -> B): NonEmptyList { 10 | return NonEmptyList(list.map(f)) 11 | } 12 | 13 | companion object { 14 | 15 | fun unsafe(list: List) = NonEmptyList(list) 16 | 17 | fun of(a: A, vararg more: A): NonEmptyList { 18 | return NonEmptyList(listOf(a) + more) 19 | } 20 | } 21 | } 22 | 23 | operator fun NonEmptyList.plus(other: List): NonEmptyList { 24 | return NonEmptyList(list + other) 25 | } 26 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/internal/DecodeModeValidator.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.internal 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.fp.NonEmptyList 6 | import com.sksamuel.hoplite.fp.invalid 7 | import com.sksamuel.hoplite.fp.valid 8 | 9 | /** 10 | * Checks results based on [DecodeMode]. 11 | */ 12 | class DecodeModeValidator(private val mode: DecodeMode) { 13 | 14 | fun validate(a: A, state: DecodingState): ConfigResult { 15 | return when (mode) { 16 | DecodeMode.Strict -> ensureAllUsed(state).map { a } 17 | DecodeMode.Lenient -> a.valid() 18 | } 19 | } 20 | 21 | private fun ensureAllUsed(result: DecodingState): ConfigResult { 22 | return if (result.unused.isEmpty()) result.valid() else { 23 | val errors = NonEmptyList.unsafe(result.unused.map { ConfigFailure.UnusedPath(it) }) 24 | ConfigFailure.MultipleFailures(errors).invalid() 25 | } 26 | } 27 | } 28 | 29 | enum class DecodeMode { 30 | // errors if a config value is provided but not used 31 | Strict, 32 | 33 | // allows config to be unused 34 | Lenient 35 | } 36 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/internal/Preprocessing.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.internal 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.fp.Validated 8 | import com.sksamuel.hoplite.fp.flatMap 9 | import com.sksamuel.hoplite.fp.valid 10 | import com.sksamuel.hoplite.preprocessor.Preprocessor 11 | 12 | /** 13 | * Applies the pre-processing steps to a [Node] tree. 14 | */ 15 | class Preprocessing( 16 | private val preprocessors: List, 17 | private val iterations: Int 18 | ) { 19 | 20 | fun preprocess(node: Node, context: DecoderContext): ConfigResult { 21 | return iterate(node, iterations, context) 22 | } 23 | 24 | private fun iterate(node: Node, iterations: Int, context: DecoderContext): Validated = 25 | if (iterations == 0) node.valid() else process(node, context).flatMap { iterate(it, iterations - 1, context) } 26 | 27 | private fun process(node: Node, context: DecoderContext): Validated = 28 | preprocessors.fold>(node.valid()) { acc, preprocessor -> 29 | acc.flatMap { preprocessor.process(it, context) } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/parsers/PropsParser.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.parsers 2 | 3 | import com.sksamuel.hoplite.ConfigResult 4 | import com.sksamuel.hoplite.Node 5 | import com.sksamuel.hoplite.PropertySource 6 | import com.sksamuel.hoplite.PropertySourceContext 7 | import com.sksamuel.hoplite.fp.valid 8 | import java.io.InputStream 9 | import java.io.InputStreamReader 10 | import java.nio.charset.Charset 11 | import java.util.Properties 12 | 13 | class PropsParser : Parser { 14 | 15 | override fun load(input: InputStream, source: String): Node { 16 | val props = Properties() 17 | props.load(InputStreamReader(input, Charset.forName("UTF-8"))) 18 | return props.toNode(source) 19 | } 20 | 21 | override fun defaultFileExtensions(): List = listOf("props", "properties") 22 | } 23 | 24 | class PropsPropertySource(val props: Properties, val name: String = "props") : PropertySource { 25 | 26 | override fun node(context: PropertySourceContext): ConfigResult { 27 | return props.toNode(name).valid() 28 | } 29 | 30 | override fun source(): String = name 31 | } 32 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/context/ContextResolverMode.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.context 2 | 3 | enum class ContextResolverMode { 4 | 5 | // do not fail if the substitution path is not found, leaving the expression intact. 6 | SkipUnresolved, 7 | 8 | // fail if the substitution path is not found 9 | ErrorOnUnresolved, 10 | } 11 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/context/EnvVarContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.context 2 | 3 | import com.sksamuel.hoplite.ConfigResult 4 | import com.sksamuel.hoplite.DecoderContext 5 | import com.sksamuel.hoplite.Node 6 | import com.sksamuel.hoplite.StringNode 7 | import com.sksamuel.hoplite.fp.valid 8 | 9 | /** 10 | * Replaces strings of the form ${{ env:name }} by looking up the name as an environment variable. 11 | * Defaults can also be applied in case the env var does not exist: ${{ env:name :- default }} 12 | */ 13 | object EnvVarContextResolver : ContextResolver() { 14 | 15 | override val contextKey: String = "env" 16 | override val default: Boolean = true 17 | 18 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 19 | return System.getenv(path).valid() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/context/HopliteContextResolver.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("RegExpRedundantEscape") 2 | 3 | package com.sksamuel.hoplite.resolver.context 4 | 5 | import com.sksamuel.hoplite.ConfigFailure 6 | import com.sksamuel.hoplite.ConfigResult 7 | import com.sksamuel.hoplite.DecoderContext 8 | import com.sksamuel.hoplite.Node 9 | import com.sksamuel.hoplite.StringNode 10 | import com.sksamuel.hoplite.fp.invalid 11 | import com.sksamuel.hoplite.fp.valid 12 | 13 | /** 14 | * Replaces strings of the form ${{ hoplite:env }} by using the [com.sksamuel.hoplite.env.Environment] value 15 | * provided to the config loader. Defaults can also be applied in case the environment does not 16 | * exist, eg: ${{ hoplite:environment :- default }} 17 | */ 18 | object HopliteContextResolver : ContextResolver() { 19 | 20 | override val contextKey: String = "hoplite" 21 | override val default: Boolean = true 22 | 23 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 24 | return when (path) { 25 | "env" -> Runtime.getRuntime().availableProcessors().toString().valid() 26 | else -> ConfigFailure.ResolverFailure("Uknown hoplite context path $path").invalid() 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/context/ManifestContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.context 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.invalid 9 | import com.sksamuel.hoplite.fp.valid 10 | import java.util.jar.Manifest 11 | 12 | object ManifestContextResolver : ContextResolver() { 13 | 14 | override val contextKey: String = "manifest" 15 | override val default: Boolean = true 16 | 17 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 18 | return runCatching { 19 | val input = javaClass.getResourceAsStream("/META-INF/MANIFEST.MF") 20 | if (input == null) ConfigFailure.ResolverFailure("Manifest could not be located").invalid() else { 21 | val manifest = Manifest(input) 22 | manifest.mainAttributes.getValue(path).valid() 23 | } 24 | }.getOrElse { 25 | ConfigFailure.ResolverException("Error loading manifest", it).invalid() 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/context/ReferenceContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.context 2 | 3 | import com.sksamuel.hoplite.BooleanNode 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.DoubleNode 7 | import com.sksamuel.hoplite.LongNode 8 | import com.sksamuel.hoplite.Node 9 | import com.sksamuel.hoplite.StringNode 10 | import com.sksamuel.hoplite.fp.Validated 11 | import com.sksamuel.hoplite.fp.valid 12 | 13 | /** 14 | * Replaces strings of the form ${{ ref://path }} by looking up the path in the parsed config. 15 | * Defaults can also be applied in case the path does not exist: ${{ ref://path:-default }} 16 | */ 17 | object ReferenceContextResolver : ContextResolver() { 18 | 19 | override val contextKey = "ref" 20 | override val default = true 21 | 22 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult = 23 | when (val n = root.atPath(path)) { 24 | is StringNode -> n.value.valid() 25 | is LongNode -> n.value.toString().valid() 26 | is DoubleNode -> n.value.toString().valid() 27 | is BooleanNode -> n.value.toString().valid() 28 | else -> Validated.Valid(null) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/context/SystemContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.context 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.invalid 9 | import com.sksamuel.hoplite.fp.valid 10 | 11 | object SystemContextResolver : ContextResolver() { 12 | 13 | override val contextKey: String = "system" 14 | override val default: Boolean = true 15 | 16 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 17 | return when (path) { 18 | "processors" -> Runtime.getRuntime().availableProcessors().toString().valid() 19 | "timestamp" -> System.currentTimeMillis().toString().valid() 20 | else -> ConfigFailure.ResolverFailure("Uknown system context path $path").invalid() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/context/SystemPropertyContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.context 2 | 3 | import com.sksamuel.hoplite.ConfigResult 4 | import com.sksamuel.hoplite.DecoderContext 5 | import com.sksamuel.hoplite.Node 6 | import com.sksamuel.hoplite.StringNode 7 | import com.sksamuel.hoplite.fp.valid 8 | 9 | /** 10 | * Replaces strings of the form ${{ sysprop:name }} by looking up the name as a system property. 11 | * Defaults can also be applied in case the system property does not exist: ${{ sysprop:name :- default }} 12 | */ 13 | object SystemPropertyContextResolver : ContextResolver() { 14 | 15 | override val contextKey = "sysprop" 16 | override val default = true 17 | 18 | override fun lookup(path: String, node: StringNode, root: Node, context: DecoderContext): ConfigResult { 19 | return System.getProperty(path).valid() 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/validator/HostnameValidator.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.validator 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.fp.invalid 9 | import com.sksamuel.hoplite.fp.valid 10 | import com.sksamuel.hoplite.resolver.Resolver 11 | import java.net.InetAddress 12 | import kotlin.reflect.KClass 13 | import kotlin.time.Duration 14 | import kotlin.time.Duration.Companion.seconds 15 | 16 | class HostnameValidator(private val timeout: Duration = 5.seconds) : Resolver { 17 | 18 | override suspend fun resolve( 19 | paramName: String?, 20 | kclass: KClass<*>, 21 | node: Node, 22 | root: Node, 23 | context: DecoderContext 24 | ): ConfigResult { 25 | return if (node is StringNode && (paramName == "hostname" || paramName == "host")) { 26 | val hostname = node.value 27 | val failure = ConfigFailure.ValidationFailed("Hostname `$hostname` not reachable", node).invalid() 28 | runCatching { 29 | val reachable = InetAddress.getByName(hostname).isReachable(timeout.inWholeMilliseconds.toInt()) 30 | if (reachable) node.valid() else failure 31 | }.getOrElse { failure } 32 | } else { 33 | node.valid() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/resolver/validator/PortValidator.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver.validator 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.fp.invalid 8 | import com.sksamuel.hoplite.fp.valid 9 | import com.sksamuel.hoplite.resolver.Resolver 10 | import com.sksamuel.hoplite.valueOrNull 11 | import kotlin.reflect.KClass 12 | 13 | class PortValidator : Resolver { 14 | override suspend fun resolve( 15 | paramName: String?, 16 | kclass: KClass<*>, 17 | node: Node, 18 | root: Node, 19 | context: DecoderContext 20 | ): ConfigResult { 21 | return if (paramName == "port") { 22 | val port = node.valueOrNull()?.toLongOrNull() ?: 0L 23 | return if (port > 0 && port < Short.MAX_VALUE) node.valid() else 24 | ConfigFailure.ValidationFailed("Invalid port: $port", node).invalid() 25 | } else node.valid() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/sources/InputStreamPropertySource.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.sources 2 | 3 | import com.sksamuel.hoplite.ConfigResult 4 | import com.sksamuel.hoplite.Node 5 | import com.sksamuel.hoplite.PropertySource 6 | import com.sksamuel.hoplite.PropertySourceContext 7 | import java.io.InputStream 8 | 9 | /** 10 | * An implementation of [PropertySource] that provides config via an [InputStream]. 11 | * You must specify the config type in addition to the stream source. 12 | * 13 | * @param input the input stream that contains the config. 14 | * 15 | * @param ext the file extension that will be used in the parser registry to locate the 16 | * correct parser to use. For example, pass in "yml" if the input stream represents a yml file. 17 | * It is important the right extension type is passed in, because the input stream doesn't itself 18 | * offer any indication what type of file it contains. 19 | */ 20 | class InputStreamPropertySource( 21 | private val input: InputStream, 22 | private val ext: String, 23 | private val source: String 24 | ) : PropertySource { 25 | 26 | override fun source(): String = source 27 | 28 | override fun node(context: PropertySourceContext): ConfigResult { 29 | return context.parsers.locate(ext).map { 30 | it.load(input, source) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/sources/MapPropertySource.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.sources 2 | 3 | import com.sksamuel.hoplite.ConfigResult 4 | import com.sksamuel.hoplite.Node 5 | import com.sksamuel.hoplite.PropertySource 6 | import com.sksamuel.hoplite.PropertySourceContext 7 | import com.sksamuel.hoplite.Undefined 8 | import com.sksamuel.hoplite.fp.valid 9 | import com.sksamuel.hoplite.parsers.toNode 10 | 11 | /** 12 | * An implementation of [PropertySource] that simply wraps a [Map]. 13 | */ 14 | class MapPropertySource( 15 | private val map: Map 16 | ) : PropertySource { 17 | 18 | override fun source(): String = "Provided Map" 19 | 20 | override fun node(context: PropertySourceContext): ConfigResult { 21 | return when { 22 | map.isEmpty() -> Undefined.valid() 23 | else -> map.toNode("map").valid() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/transformer/NodeTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.transformer 2 | 3 | import com.sksamuel.hoplite.* 4 | 5 | /** 6 | * A [NodeTransformer] is a function that transforms a node into another node. Any type of node transformation can 7 | * be applied at configuration loading time. 8 | */ 9 | interface NodeTransformer { 10 | /** Used for one of path element transformations equivalent to the node transformation. */ 11 | fun transformPathElement(element: String): String 12 | 13 | fun transform(node: Node, sealedTypeDiscriminatorField: String?): Node 14 | } 15 | -------------------------------------------------------------------------------- /hoplite-core/src/main/kotlin/com/sksamuel/hoplite/types.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | typealias Masked = Secret 4 | 5 | data class Secret(val value: String) { 6 | override fun toString(): String = "****" 7 | } 8 | -------------------------------------------------------------------------------- /hoplite-core/src/main/resources/META-INF/services/com.sksamuel.hoplite.parsers.Parser: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.parsers.PropsParser 2 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/CascadingNormalizationTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import com.sksamuel.hoplite.sources.EnvironmentVariablesPropertySource 4 | import com.sksamuel.hoplite.sources.MapPropertySource 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class CascadingNormalizationTest : FunSpec() { 9 | init { 10 | test("Parameter normalization works with cascading") { 11 | data class SubSection(val someValue: Int) 12 | data class Section(val test: Int, val subSection: SubSection) 13 | data class TestConfig(val section: Section) 14 | 15 | val configInputs = mapOf("section" to mapOf("test" to 1, "sub-section" to mapOf("some-value" to 2))) 16 | 17 | val config = ConfigLoaderBuilder.defaultWithoutPropertySources() 18 | .addPropertySource( 19 | EnvironmentVariablesPropertySource( 20 | environmentVariableMap = { mapOf("SECTION_SUBSECTION_SOMEVALUE" to "3") } 21 | ) 22 | ) 23 | .addPropertySource(MapPropertySource(configInputs)) 24 | .build() 25 | .loadConfigOrThrow() 26 | 27 | config.section.subSection.someValue shouldBe 3 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/EmptyDecoderRegistryTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import com.sksamuel.hoplite.decoder.DecoderRegistry 4 | import com.sksamuel.hoplite.fp.Validated 5 | import com.sksamuel.hoplite.parsers.defaultParserRegistry 6 | import io.kotest.core.spec.style.FunSpec 7 | import io.kotest.matchers.shouldBe 8 | 9 | class EmptyDecoderRegistryTest : FunSpec() { 10 | init { 11 | test("empty decoder registry throws error") { 12 | data class Config(val a: String) 13 | 14 | val parsers = defaultParserRegistry() 15 | val sources = defaultPropertySources() 16 | val preprocessors = defaultPreprocessors() 17 | val nodeTransformers = defaultNodeTransformers() 18 | val mappers = defaultParamMappers() 19 | val e = ConfigLoader( 20 | DecoderRegistry.zero, 21 | sources, 22 | parsers, 23 | preprocessors, 24 | nodeTransformers, 25 | mappers, 26 | allowEmptyTree = false, 27 | allowUnresolvedSubstitutions = false, 28 | sealedTypeDiscriminatorField = null, 29 | allowNullOverride = false, 30 | ).loadConfig() 31 | e as Validated.Invalid 32 | e.error shouldBe ConfigFailure.EmptyDecoderRegistry 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/Github350.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import com.sksamuel.hoplite.internal.CascadeMode 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | @OptIn(ExperimentalHoplite::class) 8 | class Github350 : FunSpec() { 9 | init { 10 | test("CascadeMode.Override doesn't work #350") { 11 | 12 | val config: Config = ConfigLoader.builder() 13 | .addResourceOrFileSource("/.env.properties") // must always be last as it contains the super secret stuff and is ignored by git. 14 | .addResourceOrFileSource("/a1.properties") 15 | .addResourceOrFileSource("/application.properties") // the contents of this file should be ignored, but is not (see google credentials) 16 | .withCascadeMode(CascadeMode.Merge) 17 | .build() 18 | .loadConfigOrThrow() 19 | 20 | val expected = Config( 21 | GoogleCredentials(".env.properties", ".env.properties"), 22 | Database("a1.properties", "a1.properties") 23 | ) 24 | 25 | config shouldBe expected 26 | } 27 | } 28 | } 29 | 30 | data class Config( 31 | val googleCredentials: GoogleCredentials, 32 | val database: Database 33 | ) 34 | 35 | data class GoogleCredentials( 36 | val clientId: String, 37 | val clientSecret: String 38 | ) 39 | 40 | data class Database( 41 | val jdbcUrl: String, 42 | val driverClassName: String 43 | ) 44 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/InvalidConstructorArgsTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import com.sksamuel.hoplite.fp.Validated 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class InvalidConstructorArgsTest : FunSpec() { 8 | init { 9 | test("invalid constructor args") { 10 | // linked hash set is not supported, so this should cause a normal set to be picked up 11 | // which will in turn cause the constructor to baulk 12 | data class Config(val e: LinkedHashSet) 13 | val e = ConfigLoader().loadConfig("/basic.props") 14 | e as Validated.Invalid 15 | e.error.description() shouldBe "Could not instantiate class com.sksamuel.hoplite.InvalidConstructorArgsTest\$1\$Config from args [java.util.Collections\$SingletonSet]: Expected args are [class java.util.LinkedHashSet]. Underlying error was java.lang.IllegalArgumentException: argument type mismatch" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/NoValuesTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import io.kotest.assertions.throwables.shouldThrow 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import io.kotest.matchers.string.shouldContain 7 | 8 | class NoValuesTest : FunSpec({ 9 | val emptyConfigLoader = ConfigLoaderBuilder 10 | .defaultWithoutPropertySources() 11 | .addPropertySources(emptyByDefaultPropertySources()) 12 | .build() 13 | 14 | test("ConfigLoader should return UndefinedTree if all sources were Invalid") { 15 | emptyConfigLoader.loadNode().getInvalidUnsafe() shouldBe ConfigFailure.UndefinedTree 16 | } 17 | 18 | test("ConfigLoader should return meaningful error if no sources return a value") { 19 | shouldThrow { 20 | emptyConfigLoader.loadNodeOrThrow() 21 | }.message shouldContain "The applied config was empty" 22 | } 23 | 24 | test("ConfigLoader should ignore no values when option is enabled") { 25 | data class NestedConfig( 26 | val nums: List = emptyList() 27 | ) 28 | 29 | data class Test( 30 | val name: String = "a", 31 | val nested: NestedConfig = NestedConfig() 32 | ) 33 | 34 | ConfigLoaderBuilder.default() 35 | .allowEmptyConfigFiles() 36 | .build() 37 | .loadConfigOrThrow() 38 | .name shouldBe "a" 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/decoder/BooleanArrayDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class BooleanArrayDecoderTest : StringSpec({ 8 | 9 | "boolean array decoder should decode delimited strings" { 10 | data class Test(val a: BooleanArray) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_boolean_array.props") 13 | config.a shouldBe booleanArrayOf(true, false, true) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/decoder/ByteArrayDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class ByteArrayDecoderTest : StringSpec({ 8 | 9 | "byte array decoder should decode strings" { 10 | data class Test(val a: ByteArray) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_byte_array.props") 13 | config.a shouldBe byteArrayOf(104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/decoder/InlineClassDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.DecoderContext 5 | import com.sksamuel.hoplite.LongNode 6 | import com.sksamuel.hoplite.Pos 7 | import com.sksamuel.hoplite.defaultNodeTransformers 8 | import com.sksamuel.hoplite.defaultParamMappers 9 | import com.sksamuel.hoplite.fp.Validated 10 | import io.kotest.core.spec.style.StringSpec 11 | import io.kotest.matchers.shouldBe 12 | import io.kotest.matchers.types.shouldBeInstanceOf 13 | import kotlin.reflect.full.createType 14 | 15 | class InlineClassDecoderTest : StringSpec({ 16 | 17 | "should handle illegal argument exceptions gracefully" { 18 | val node = LongNode(-1, Pos.NoPos, DotPath.root) 19 | 20 | InlineClassDecoder().decode( 21 | node, Port::class.createType(), 22 | DecoderContext(defaultDecoderRegistry(), defaultParamMappers(), defaultNodeTransformers()) 23 | ) 24 | .shouldBeInstanceOf>() 25 | .error.e.shouldBeInstanceOf() 26 | .message shouldBe "Invalid port: -1" 27 | } 28 | }) { 29 | @JvmInline 30 | value class Port(val value: Int) { 31 | init { 32 | require(value in 0..65535) { "Invalid port: $value" } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/decoder/LocalTimeDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ClasspathResourceLoader.Companion.toClasspathResourceLoader 4 | import com.sksamuel.hoplite.ConfigLoader 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | import java.time.LocalTime 8 | 9 | class LocalTimeDecoderTest : FunSpec() { 10 | init { 11 | test("test local time") { 12 | data class Config(val a: LocalTime, val b: LocalTime) 13 | val config = ConfigLoader().loadConfigOrThrow("/localtime.props") 14 | config.a shouldBe LocalTime.parse("18:15") 15 | config.b shouldBe LocalTime.parse("18:15:59") 16 | } 17 | test("test local time with thread classloader") { 18 | data class Config(val a: LocalTime, val b: LocalTime) 19 | 20 | val config = ConfigLoader().loadConfigOrThrow( 21 | listOf("localtime.props"), 22 | Thread.currentThread().contextClassLoader.toClasspathResourceLoader() 23 | ) 24 | config.a shouldBe LocalTime.parse("18:15") 25 | config.b shouldBe LocalTime.parse("18:15:59") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/decoder/SizeInBytesDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import io.kotest.core.spec.style.FunSpec 4 | import io.kotest.matchers.shouldBe 5 | 6 | class SizeInBytesDecoderTest : FunSpec({ 7 | val units = listOf( 8 | InformationUnit.Kilobytes, 9 | InformationUnit.Megabytes, 10 | InformationUnit.Gigabytes, 11 | 12 | InformationUnit.Octets, 13 | InformationUnit.Kibibytes, 14 | InformationUnit.Mebibytes, 15 | InformationUnit.Gibibytes 16 | ) 17 | 18 | test("conversion between bytes and other units") { 19 | val size = 8 20 | for (unit in units) { 21 | SizeInBytes((size * unit.ratioToPrimary).toLong()).convert(unit) shouldBe size 22 | } 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/decoder/TripleDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class TripleDecoderTest : StringSpec({ 8 | 9 | "triple should be decoded from string with 3 fields" { 10 | data class Test(val a: Triple, val b: Triple) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_triple.props") 13 | config shouldBe Test(Triple("hello", "world", true), Triple("5", 4L, 3)) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/report/PrefixObfuscatorTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.report 2 | 3 | import com.sksamuel.hoplite.BooleanNode 4 | import com.sksamuel.hoplite.DoubleNode 5 | import com.sksamuel.hoplite.LongNode 6 | import com.sksamuel.hoplite.Pos 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.decoder.DotPath 9 | import com.sksamuel.hoplite.secrets.PrefixObfuscator 10 | import io.kotest.core.spec.style.FunSpec 11 | import io.kotest.matchers.shouldBe 12 | 13 | class PrefixObfuscatorTest : FunSpec({ 14 | 15 | test("happy path") { 16 | PrefixObfuscator(3).obfuscate(StringNode("foo", Pos.NoPos, DotPath.root)).shouldBe("foo") 17 | PrefixObfuscator(3).obfuscate(StringNode("foobar", Pos.NoPos, DotPath.root)).shouldBe("foo*****") 18 | PrefixObfuscator(3).obfuscate(StringNode("foobarfoobar", Pos.NoPos, DotPath.root)).shouldBe("foo*****") 19 | PrefixObfuscator(3).obfuscate(StringNode("f", Pos.NoPos, DotPath.root)).shouldBe("f") 20 | PrefixObfuscator(3).obfuscate(StringNode("", Pos.NoPos, DotPath.root)).shouldBe("") 21 | } 22 | 23 | test("non-strings should not be obfucscatd") { 24 | PrefixObfuscator(3).obfuscate(BooleanNode(true, Pos.NoPos, DotPath.root)).shouldBe("true") 25 | PrefixObfuscator(3).obfuscate(LongNode(324, Pos.NoPos, DotPath.root)).shouldBe("324") 26 | PrefixObfuscator(3).obfuscate(DoubleNode(1.23, Pos.NoPos, DotPath.root)).shouldBe("1.23") 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /hoplite-core/src/test/kotlin/com/sksamuel/hoplite/resolver/ManifestContextResolverTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.resolver 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.ExperimentalHoplite 5 | import com.sksamuel.hoplite.parsers.PropsPropertySource 6 | import io.kotest.core.spec.style.FunSpec 7 | import io.kotest.matchers.shouldBe 8 | import java.util.Properties 9 | 10 | @OptIn(ExperimentalHoplite::class) 11 | class ManifestContextResolverTest : FunSpec({ 12 | 13 | data class Config(val foo: String) 14 | 15 | test("should support manifest lookup") { 16 | 17 | val props = Properties() 18 | props["foo"] = "boaty \${{manifest:Manifest-Version}} face" 19 | 20 | val config = ConfigLoaderBuilder.newBuilder() 21 | .addPropertySource(PropsPropertySource(props)) 22 | .build() 23 | .loadConfigOrThrow() 24 | 25 | config shouldBe Config(foo = "boaty 1.0 face") 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/.env.properties: -------------------------------------------------------------------------------- 1 | googleCredentials.clientId=.env.properties 2 | googleCredentials.clientSecret=.env.properties 3 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 1.7.0_06 3 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/a1.properties: -------------------------------------------------------------------------------- 1 | googleCredentials.clientId=a1.properties 2 | googleCredentials.clientSecret=a1.properties 3 | database.jdbcUrl=a1.properties 4 | database.driverClassName=a1.properties 5 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/advanced.props: -------------------------------------------------------------------------------- 1 | a = foo 2 | a.b.c = wibble 3 | a.b.d = 123 4 | a.d = true 5 | e = 5.5 6 | e.f = 6 7 | e.f.g = goo 8 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | googleCredentials.clientId=qweqw 2 | googleCredentials.clientSecret=rtyt 3 | database.jdbcUrl=application.properties 4 | database.driverClassName=application.properties 5 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/basic.props: -------------------------------------------------------------------------------- 1 | a.b.c = wibble 2 | a.b.d = 123 3 | a.g = true 4 | a.b = qqq 5 | e = 5.5 6 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/empty.props: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sksamuel/hoplite/e211e07c369b40a7dc9a706f1f0d144a9c457266/hoplite-core/src/test/resources/empty.props -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/localtime.props: -------------------------------------------------------------------------------- 1 | a=18:15 2 | b=18:15:59 3 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/prefixProcessor.props: -------------------------------------------------------------------------------- 1 | a=This is the quiet part 2 | b=upcase:You kids get off my lawn 3 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/processme.props: -------------------------------------------------------------------------------- 1 | a=I'm on branch ${git.branch} 2 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/sample.properties: -------------------------------------------------------------------------------- 1 | git.branch=master 2 | git.build.host=sam-hp 3 | git.build.user.email=sam@sksamuel.com 4 | git.build.user.name=sksamuel 5 | git.build.version=1.0.6 6 | git.closest.tag.commit.count= 7 | git.closest.tag.name= 8 | git.commit.id=47854c215b57b69871d316ea694e8278e998c176 9 | git.commit.id.abbrev=47854c2 10 | git.commit.id.describe= 11 | git.commit.message.full=Removed jackson bom\n 12 | git.commit.message.short=Removed jackson bom 13 | git.commit.time=2019-09-10T06\:55\:28-0500 14 | git.commit.user.email=sam@sksamuel.com 15 | git.commit.user.name=sksamuel 16 | git.dirty=true 17 | git.remote.origin.url=git@github.com\:sksamuel/hoplite.git 18 | git.tags= 19 | git.total.commit.count=187 20 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/test_boolean_array.props: -------------------------------------------------------------------------------- 1 | a=true, false, true 2 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/test_byte_array.props: -------------------------------------------------------------------------------- 1 | a=hello world 2 | -------------------------------------------------------------------------------- /hoplite-core/src/test/resources/test_triple.props: -------------------------------------------------------------------------------- 1 | a=hello,world,true 2 | b=5,4,3 3 | -------------------------------------------------------------------------------- /hoplite-cronutils/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | api(libs.cron.utils) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-cronutils/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.cronutils.CronDecoder 2 | -------------------------------------------------------------------------------- /hoplite-cronutils/src/test/kotlin/com/sksamuel/hoplite/CronDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import com.cronutils.model.Cron 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | 7 | class CronDecoderTest : StringSpec() { 8 | init { 9 | "cron decoder" { 10 | data class Config(val a: Cron) 11 | ConfigLoader().loadConfigOrThrow("/cron.props").a.asString() shouldBe "0 0 * * *" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hoplite-cronutils/src/test/resources/cron.props: -------------------------------------------------------------------------------- 1 | a=0 0 * * * 2 | -------------------------------------------------------------------------------- /hoplite-datetime/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.kotlinx.datetime) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-datetime/src/main/kotlin/com/sksamuel/hoplite/datetime/InstantDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.datetime 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.LongNode 7 | import com.sksamuel.hoplite.Node 8 | import com.sksamuel.hoplite.StringNode 9 | import com.sksamuel.hoplite.decoder.NonNullableLeafDecoder 10 | import com.sksamuel.hoplite.decoder.toValidated 11 | import com.sksamuel.hoplite.fp.invalid 12 | import com.sksamuel.hoplite.fp.valid 13 | import kotlinx.datetime.Instant 14 | import kotlin.reflect.KType 15 | 16 | class InstantDecoder : NonNullableLeafDecoder { 17 | override fun supports(type: KType): Boolean = type.classifier == Instant::class 18 | override fun safeLeafDecode(node: Node, 19 | type: KType, 20 | context: DecoderContext): ConfigResult = when (node) { 21 | is StringNode -> runCatching { Instant.fromEpochMilliseconds(node.value.toLong()) }.recoverCatching { Instant.parse(node.value) }.toValidated { 22 | ConfigFailure.DecodeError(node, type) 23 | } 24 | is LongNode -> Instant.fromEpochMilliseconds(node.value).valid() 25 | else -> ConfigFailure.DecodeError(node, type).invalid() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hoplite-datetime/src/main/kotlin/com/sksamuel/hoplite/datetime/LocalDateDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.datetime 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.decoder.NonNullableLeafDecoder 9 | import com.sksamuel.hoplite.decoder.toValidated 10 | import com.sksamuel.hoplite.fp.invalid 11 | import kotlinx.datetime.LocalDate 12 | import kotlin.reflect.KType 13 | 14 | class LocalDateDecoder : NonNullableLeafDecoder { 15 | override fun supports(type: KType): Boolean = type.classifier == LocalDate::class 16 | override fun safeLeafDecode(node: Node, 17 | type: KType, 18 | context: DecoderContext 19 | ): ConfigResult = when (node) { 20 | is StringNode -> runCatching { LocalDate.parse(node.value) }.toValidated { 21 | ConfigFailure.DecodeError(node, type) 22 | } 23 | else -> ConfigFailure.DecodeError(node, type).invalid() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hoplite-datetime/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.datetime.InstantDecoder 2 | com.sksamuel.hoplite.datetime.LocalDateTimeDecoder 3 | com.sksamuel.hoplite.datetime.LocalDateDecoder 4 | -------------------------------------------------------------------------------- /hoplite-datetime/src/test/kotlin/com/sksamuel/hoplite/datetime/InstantDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.datetime 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import kotlinx.datetime.Instant 7 | 8 | class InstantDecoderTest : FunSpec() { 9 | init { 10 | test("test instant") { 11 | data class Config(val a: Instant) 12 | ConfigLoader().loadConfigOrThrow("/instant.props").a shouldBe Instant.fromEpochMilliseconds(1423131323423) 13 | } 14 | test("test iso instant") { 15 | data class Config(val a: Instant) 16 | ConfigLoader().loadConfigOrThrow("/instant_iso.props").a shouldBe Instant.parse("2007-12-03T10:15:30.00Z") 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hoplite-datetime/src/test/kotlin/com/sksamuel/hoplite/datetime/LocalDateDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.datetime 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import kotlinx.datetime.LocalDate 7 | 8 | class LocalDateDecoderTest : FunSpec() { 9 | init { 10 | test("test local date") { 11 | data class Config(val a: LocalDate) 12 | ConfigLoader().loadConfigOrThrow("/localdate.props").a shouldBe LocalDate.parse("1974-08-10") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hoplite-datetime/src/test/kotlin/com/sksamuel/hoplite/datetime/LocalDateTimeDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.datetime 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import kotlinx.datetime.Instant 7 | import kotlinx.datetime.LocalDateTime 8 | import kotlinx.datetime.TimeZone 9 | import kotlinx.datetime.toLocalDateTime 10 | 11 | class LocalDateTimeDecoderTest : FunSpec() { 12 | init { 13 | test("test local date time") { 14 | println(Instant.fromEpochMilliseconds(145354234234).toLocalDateTime(TimeZone.UTC).toString()) 15 | data class Config(val a: LocalDateTime) 16 | ConfigLoader().loadConfigOrThrow("/localdatetime.props").a shouldBe LocalDateTime.parse("1974-08-10T08:10:34.234") 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hoplite-datetime/src/test/resources/instant.props: -------------------------------------------------------------------------------- 1 | a=1423131323423 2 | -------------------------------------------------------------------------------- /hoplite-datetime/src/test/resources/instant_iso.props: -------------------------------------------------------------------------------- 1 | a=2007-12-03T10:15:30.00Z 2 | -------------------------------------------------------------------------------- /hoplite-datetime/src/test/resources/localdate.props: -------------------------------------------------------------------------------- 1 | a=1974-08-10 2 | -------------------------------------------------------------------------------- /hoplite-datetime/src/test/resources/localdatetime.props: -------------------------------------------------------------------------------- 1 | a=1974-08-10T08:10:34.234 2 | -------------------------------------------------------------------------------- /hoplite-gcp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | api(libs.google.cloud.secretmanager) 4 | testApi(libs.kotest.extensions.testcontainers) 5 | } 6 | 7 | apply("../publish.gradle.kts") 8 | -------------------------------------------------------------------------------- /hoplite-gcp/src/main/kotlin/com/sksamuel/hoplite/gcp/GcpSecretManagerContextResolver.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.gcp 2 | 3 | import com.google.cloud.secretmanager.v1.SecretManagerServiceClient 4 | 5 | class GcpSecretManagerContextResolver( 6 | report: Boolean = false, 7 | createClient: () -> SecretManagerServiceClient, 8 | ) : AbstractGcpSecretManagerContextResolver(report, createClient) { 9 | override val contextKey: String = "gcp-secrets-manager" 10 | override val default: Boolean = false 11 | } 12 | -------------------------------------------------------------------------------- /hoplite-hdfs/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | api(libs.hadoop.common) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-hdfs/src/main/kotlin/com/sksamuel/hoplite/hdfs/PathDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.hdfs 2 | 3 | import com.sksamuel.hoplite.ConfigFailure 4 | import com.sksamuel.hoplite.ConfigResult 5 | import com.sksamuel.hoplite.DecoderContext 6 | import com.sksamuel.hoplite.Node 7 | import com.sksamuel.hoplite.StringNode 8 | import com.sksamuel.hoplite.decoder.NullHandlingDecoder 9 | import com.sksamuel.hoplite.fp.invalid 10 | import com.sksamuel.hoplite.fp.valid 11 | import org.apache.hadoop.fs.Path 12 | import kotlin.reflect.KType 13 | import kotlin.reflect.full.createType 14 | 15 | class PathDecoder : NullHandlingDecoder { 16 | 17 | override fun supports(type: KType): Boolean = type.classifier == Path::class 18 | 19 | override fun safeDecode(node: Node, 20 | type: KType, 21 | context: DecoderContext): ConfigResult { 22 | return when (node) { 23 | is StringNode -> Path(node.value).valid() 24 | else -> ConfigFailure.DecodeError(node, Path::class.createType()).invalid() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hoplite-hdfs/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.hdfs.PathDecoder 2 | -------------------------------------------------------------------------------- /hoplite-hikaricp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | api(libs.hikaricp) 4 | 5 | testImplementation(projects.hopliteYaml) 6 | testImplementation(libs.postgresql) 7 | } 8 | 9 | apply("../publish.gradle.kts") 10 | -------------------------------------------------------------------------------- /hoplite-hikaricp/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.hikari.HikariDataSourceDecoder 2 | -------------------------------------------------------------------------------- /hoplite-hikaricp/src/test/kotlin/com/sksamuel/hoplite/HikariDataSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite 2 | 3 | import com.zaxxer.hikari.HikariDataSource 4 | import com.zaxxer.hikari.pool.HikariPool 5 | import io.kotest.assertions.throwables.shouldThrow 6 | import io.kotest.core.spec.style.StringSpec 7 | import io.kotest.matchers.string.shouldContain 8 | 9 | class HikariDataSourceTest : StringSpec() { 10 | init { 11 | "hikari datasource decoder" { 12 | data class Config(val db: HikariDataSource) 13 | 14 | shouldThrow { 15 | ConfigLoader().loadConfigOrThrow("/hikari.yaml").db 16 | }.cause?.cause?.message shouldContain "serverhost" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hoplite-hikaricp/src/test/resources/hikari.yaml: -------------------------------------------------------------------------------- 1 | db: 2 | dataSourceClassName: org.postgresql.ds.PGSimpleDataSource 3 | dataSource: 4 | user: postgres 5 | password: postgres 6 | databaseName: postgres 7 | portNumber: 5234 8 | serverName: serverhost 9 | -------------------------------------------------------------------------------- /hoplite-hocon/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.typesafe.config) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-hocon/src/main/resources/META-INF/services/com.sksamuel.hoplite.parsers.Parser: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.hocon.HoconParser 2 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/kotlin/com/sksamuel/hoplite/hocon/FloatingPointTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.hocon 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class FloatingPointTest : FunSpec() { 8 | init { 9 | test("should support whole numbers for floats and doubles") { 10 | 11 | data class Conf(val a: Float, val b: Double) 12 | 13 | val config = ConfigLoader().loadConfigOrThrow("/floats.conf") 14 | config.a shouldBe 5.0 15 | config.b shouldBe 5.0 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/kotlin/com/sksamuel/hoplite/hocon/HoconEnvOverrideTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.hocon 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.extensions.system.withEnvironment 6 | import io.kotest.matchers.shouldBe 7 | 8 | data class Mongo(val hostname: String) 9 | data class Conf(val mongo: Mongo) 10 | 11 | class HoconEnvOverrideTest : FunSpec({ 12 | 13 | test("override hocon configuration with env variables") { 14 | withEnvironment("MONGO_HOSTNAME", "foo.wibble.com") { 15 | val config = ConfigLoader().loadConfigOrThrow("/hocon.conf") 16 | config.mongo.hostname shouldBe "foo.wibble.com" 17 | } 18 | } 19 | 20 | }) 21 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/kotlin/com/sksamuel/hoplite/hocon/InlineClassTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.hocon 2 | 3 | import com.sksamuel.hoplite.ConfigException 4 | import com.sksamuel.hoplite.ConfigLoader 5 | import io.kotest.assertions.throwables.shouldThrow 6 | import io.kotest.core.spec.style.FunSpec 7 | import io.kotest.matchers.shouldBe 8 | 9 | @JvmInline 10 | value class Port(val value: Int) 11 | @JvmInline 12 | value class Port2(val value: Boolean) 13 | 14 | class InlineClassTest : FunSpec({ 15 | 16 | test("decoding into an inline type") { 17 | data class Config(val port: Port, val host: String) 18 | ConfigLoader().loadConfigOrThrow("/valuetype.conf") shouldBe Config(Port(9200), "localhost") 19 | } 20 | 21 | test("error when an inline type is incompatible") { 22 | data class Config(val port: Port2, val host: String) 23 | shouldThrow { 24 | val config = ConfigLoader().loadConfigOrThrow("/valuetype.conf") 25 | println(config) 26 | }.message shouldBe """Error loading config because: 27 | 28 | - Could not instantiate 'com.sksamuel.hoplite.hocon.`InlineClassTest${'$'}1${'$'}2${"\$Config"}`' because: 29 | 30 | - 'port': Inline type kotlin.Boolean is incompatible with a Long value: 9200 (classpath:/valuetype.conf:2)""" 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/kotlin/com/sksamuel/hoplite/hocon/KebabSnakeCaseMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.hocon 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.WordSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class KebabSnakeCaseMapperTest : WordSpec() { 8 | 9 | init { 10 | "standard param mappers" should { 11 | "support kebab and snake case in the same file" { 12 | data class Tic(val tacToe: String, val jamJar: Double) 13 | data class Test(val wibbleWobble: String, val tic: Tic, val jeanLucPicard: String) 14 | ConfigLoader().loadConfigOrThrow("/mixed_snake_dash_case.conf") shouldBe 15 | Test(wibbleWobble = "hello", tic = Tic(tacToe = "jumble", jamJar = 5.4), jeanLucPicard = "captain") 16 | } 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/kotlin/com/sksamuel/hoplite/hocon/SealedClassLinkedHashMapTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.hocon 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.PropertySource 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class SealedClassLinkedHashMapTest : FunSpec() { 9 | init { 10 | test("linked map of sealed interface should preserve order from hocon") { 11 | ConfigLoaderBuilder.default() 12 | .addPropertySource(PropertySource.resource("/sealed_class_linked_hash_map.conf")) 13 | .build() 14 | .loadConfigOrThrow() 15 | .expressions shouldBe linkedMapOf( 16 | "const1" to Const(number = 2), 17 | "const2" to Const(number = 10), 18 | "text1" to Text(str = "This is a example text."), 19 | "const3" to Const(number = 42) 20 | ) 21 | } 22 | } 23 | } 24 | 25 | data class Configuration(val expressions: LinkedHashMap? = null) 26 | 27 | sealed interface Expr 28 | 29 | data class Const(val number: Int) : Expr 30 | data class Text(val str: String) : Expr 31 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/resources/basic.conf: -------------------------------------------------------------------------------- 1 | conf { 2 | name = "default" 3 | title = "Simple Title" 4 | nested { 5 | whitelistIds = [1, 22, 34] 6 | } 7 | complex.arrays = [ 8 | { 9 | a = wibble 10 | b = 4.4 11 | } 12 | ] 13 | missing = null 14 | } 15 | 16 | featureFlags { 17 | featureA = "yes" 18 | featureB = true 19 | } 20 | 21 | toplevel = "hello" 22 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/resources/floats.conf: -------------------------------------------------------------------------------- 1 | a: 5 2 | b: 5 3 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/resources/hocon.conf: -------------------------------------------------------------------------------- 1 | mongo { 2 | hostname = "localhost" 3 | hostname = ${?MONGO_HOSTNAME} 4 | } 5 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/resources/mixed_snake_dash_case.conf: -------------------------------------------------------------------------------- 1 | wibble-wobble: "hello" 2 | tic { 3 | tac-toe: "jumble" 4 | jam_jar = 5.4 5 | } 6 | 7 | jean_luc_picard: "captain" 8 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/resources/sealed.conf: -------------------------------------------------------------------------------- 1 | clientAuth { 2 | url = "theClientUrl" 3 | user = "2user" 4 | password = "3pass" 5 | } 6 | 7 | clientNoAuth { 8 | url = "1url" 9 | } 10 | 11 | clientWithOneSpecifiedValue { 12 | otherStuff = "1url" 13 | } 14 | 15 | clientWithTwoSpecifiedValue { 16 | otherStuff = "1url" 17 | anotherStuff = "test" 18 | } 19 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/resources/sealed_class_linked_hash_map.conf: -------------------------------------------------------------------------------- 1 | { 2 | expressions { 3 | const1 { number = 2 } 4 | const2 { number = 10 } 5 | text1 { str = "This is a example text." } 6 | const3 { number = 42 } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hoplite-hocon/src/test/resources/valuetype.conf: -------------------------------------------------------------------------------- 1 | { 2 | port: 9200, 3 | host: localhost 4 | } 5 | -------------------------------------------------------------------------------- /hoplite-javax/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | testImplementation(projects.hopliteToml) 4 | testImplementation(projects.hopliteYaml) 5 | testImplementation(projects.hopliteJson) 6 | } 7 | 8 | apply("../publish.gradle.kts") 9 | -------------------------------------------------------------------------------- /hoplite-javax/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.decoder.javax.KerberosPrincipalDecoder 2 | com.sksamuel.hoplite.decoder.javax.JMXPrincipalDecoder 3 | com.sksamuel.hoplite.decoder.javax.X500PrincipalDecoder 4 | -------------------------------------------------------------------------------- /hoplite-json/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.jackson.core) 4 | implementation(libs.jackson.databind) 5 | } 6 | 7 | apply("../publish.gradle.kts") 8 | -------------------------------------------------------------------------------- /hoplite-json/src/main/kotlin/com/sksamuel/hoplite/json/HopliteModule.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator 4 | import com.fasterxml.jackson.databind.JsonSerializer 5 | import com.fasterxml.jackson.databind.SerializerProvider 6 | import com.fasterxml.jackson.databind.module.SimpleModule 7 | import com.sksamuel.hoplite.Secret 8 | 9 | object HopliteModule : SimpleModule() { 10 | init { 11 | addSerializer(Secret::class.javaObjectType, object : JsonSerializer() { 12 | override fun serialize(value: Secret, gen: JsonGenerator, serializers: SerializerProvider?) { 13 | gen.writeString("****") 14 | } 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hoplite-json/src/main/kotlin/com/sksamuel/hoplite/json/JsonPropertyParamMapper.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.sksamuel.hoplite.ParameterMapper 5 | import kotlin.reflect.KClass 6 | import kotlin.reflect.KFunction 7 | import kotlin.reflect.KParameter 8 | import kotlin.reflect.full.findAnnotation 9 | 10 | object JsonPropertyParamMapper : ParameterMapper { 11 | override fun map(param: KParameter, constructor: KFunction, kclass: KClass<*>): Set { 12 | val jsonProperty = param.findAnnotation() 13 | val value = jsonProperty?.value ?: param.name 14 | return setOfNotNull(value) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hoplite-json/src/main/kotlin/com/sksamuel/hoplite/json/JsonPropertySource.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigResult 4 | import com.sksamuel.hoplite.Node 5 | import com.sksamuel.hoplite.PropertySource 6 | import com.sksamuel.hoplite.PropertySourceContext 7 | import com.sksamuel.hoplite.fp.valid 8 | import java.util.concurrent.atomic.AtomicInteger 9 | 10 | private val counter = AtomicInteger(0) 11 | 12 | /** 13 | * A [PropertySource] that allows providing JSON directly. 14 | * 15 | * For example: 16 | * 17 | * JsonParser( 18 | * """ { 19 | * "a": [ 20 | * { 21 | * "key": "arnold", 22 | * "value": "rimmer" 23 | * }, 24 | * { 25 | * "key": "mr", 26 | * "value": "flibble" 27 | * } 28 | * ] 29 | * } """) 30 | */ 31 | class JsonPropertySource( 32 | private val str: String, 33 | private val source: String = "Provided JSON ${counter.incrementAndGet()}" 34 | ) : PropertySource { 35 | 36 | override fun source(): String = source 37 | 38 | override fun node(context: PropertySourceContext): ConfigResult = 39 | JsonParser().load(str.byteInputStream(), source).valid() 40 | } 41 | -------------------------------------------------------------------------------- /hoplite-json/src/main/resources/META-INF/services/com.sksamuel.hoplite.parsers.Parser: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.json.JsonParser -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/BasicDecodersTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import java.math.BigDecimal 7 | import java.math.BigInteger 8 | 9 | class BasicDecodersTest : FunSpec({ 10 | 11 | test("BigDecimal") { 12 | data class Test(val a: BigDecimal, val b: BigDecimal, val c: BigDecimal, val d: BigDecimal) 13 | 14 | val config = ConfigLoader().loadConfigOrThrow("/test_bigdecimal.json") 15 | config shouldBe Test(10.toBigDecimal(), 20.3334.toBigDecimal(), 10.2.toBigDecimal(), 20.3334.toBigDecimal()) 16 | } 17 | 18 | test("BigInteger") { 19 | data class Test(val a: BigInteger, val b: BigInteger) 20 | 21 | val config = ConfigLoader().loadConfigOrThrow("/test_biginteger.json") 22 | config shouldBe Test(10000.toBigInteger(), 1231412412.toBigInteger()) 23 | } 24 | 25 | }) 26 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/EnvPropertySourceUnderscoreAsSeparatorTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.extensions.system.withEnvironment 6 | import io.kotest.matchers.shouldBe 7 | 8 | class EnvPropertySourceUnderscoreAsSeparatorTest : FunSpec({ 9 | 10 | data class Creds(val username: String, val password: String) 11 | data class Config(val creds: Creds, val someCamelSetting: String) 12 | 13 | test("loading from envs") { 14 | withEnvironment(mapOf("creds_username" to "a", "creds_password" to "b", "somecamelsetting" to "c")) { 15 | ConfigLoader() 16 | .loadConfigOrThrow() shouldBe Config(Creds("a", "b"), "c") 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/FileDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.io.File 7 | 8 | class FileDecoderTest : StringSpec({ 9 | "file decoded from json" { 10 | data class Test(val file: File) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_file.json") 13 | config shouldBe Test(File("/home/user/sam")) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/InetAddressDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.net.InetAddress 7 | 8 | class InetAddressDecoderTest : StringSpec({ 9 | "InetAddress decoded from json" { 10 | data class Test(val a: InetAddress) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_ipnet.json") 13 | config shouldBe Test(InetAddress.getByName("10.0.0.2")) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/JsonPropertyTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.sksamuel.hoplite.ConfigLoaderBuilder 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.extensions.system.withSystemProperties 7 | import io.kotest.matchers.shouldBe 8 | 9 | class JsonPropertyTest : FunSpec({ 10 | 11 | data class Config(@JsonProperty("foo") val a: String, val c: Long) 12 | 13 | test("json property on config fields") { 14 | withSystemProperties(mapOf("config.override.foo" to "x", "config.override.c" to "123")) { 15 | val config = ConfigLoaderBuilder.default() 16 | .addParameterMapper(JsonPropertyParamMapper) 17 | .build() 18 | .loadConfigOrThrow() 19 | 20 | config shouldBe Config("x", 123L) 21 | } 22 | } 23 | 24 | }) 25 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/MapTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class MapTest : FunSpec() { 8 | init { 9 | test("allow maps to be decoded from lists of two values of key/value pairs") { 10 | data class Test(val a: Map) 11 | 12 | val config = ConfigLoaderBuilder.default() 13 | .addPropertySource(JsonPropertySource( 14 | """ { 15 | "a": [ 16 | { 17 | "key": "arnold", 18 | "value": "rimmer" 19 | }, 20 | { 21 | "key": "mr", 22 | "value": "flibble" 23 | } 24 | ] 25 | } """ 26 | )) 27 | .build() 28 | .loadConfigOrThrow() 29 | config shouldBe Test(mapOf("arnold" to "rimmer", "mr" to "flibble")) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/PathDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | import java.nio.file.Path 7 | import java.nio.file.Paths 8 | 9 | class PathDecoderTest : StringSpec({ 10 | "Path decoded from json" { 11 | data class Test(val path: Path) 12 | 13 | val config = ConfigLoaderBuilder.defaultWithoutPropertySources().build() 14 | .loadConfigOrThrow("/test_path.json") 15 | config shouldBe Test(Paths.get("/home/user/sam")) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/PrincipalDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import com.sksamuel.hoplite.decoder.BasicPrincipal 5 | import io.kotest.matchers.shouldBe 6 | import io.kotest.core.spec.style.StringSpec 7 | import java.security.Principal 8 | 9 | class PrincipalDecoderTest : StringSpec({ 10 | "Principal decoded from json" { 11 | data class Test(val name: Principal) 12 | 13 | val config = ConfigLoader().loadConfigOrThrow("/principal.json") 14 | config shouldBe Test(BasicPrincipal("sammy")) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/SealedClassLinkedHashMapTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.PropertySource 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class SealedClassLinkedHashMapTest : FunSpec() { 9 | init { 10 | test("linked map of sealed interface should preserve order from json") { 11 | ConfigLoaderBuilder.default() 12 | .addPropertySource(PropertySource.resource("/sealed_class_linked_hash_map.json")) 13 | .build() 14 | .loadConfigOrThrow() 15 | .expressions shouldBe linkedMapOf( 16 | "const1" to Const(number = 2), 17 | "const2" to Const(number = 10), 18 | "text1" to Text(str = "This is a example text."), 19 | "const3" to Const(number = 42) 20 | ) 21 | } 22 | } 23 | } 24 | 25 | data class Configuration(val expressions: LinkedHashMap? = null) 26 | 27 | sealed interface Expr 28 | 29 | data class Const(val number: Int) : Expr 30 | data class Text(val str: String) : Expr 31 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/SetDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | 7 | class SetDecoderTest : StringSpec({ 8 | "set decoded from json" { 9 | data class Test(val a: Set, val b: Set) 10 | 11 | val config = ConfigLoader().loadConfigOrThrow("/sets.json") 12 | config shouldBe Test(setOf(1, 2, 3), setOf("1", "2")) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/SizeInBytesTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import com.sksamuel.hoplite.decoder.SizeInBytes 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class SizeInBytesTest : FunSpec({ 9 | test("size in bytes units") { 10 | data class Test(val a: SizeInBytes, val b: SizeInBytes, val c: SizeInBytes, val d: SizeInBytes) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/size_in_bytes.json") 13 | config shouldBe Test(SizeInBytes(10), SizeInBytes(12000000), SizeInBytes(57671680), SizeInBytes(44000000000)) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/SortedSetDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.util.* 7 | 8 | class SortedSetDecoderTest : StringSpec({ 9 | "sorted set decoded from json" { 10 | data class Test(val a: SortedSet, val b: SortedSet) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/sets.json") 13 | config shouldBe Test(sortedSetOf(1, 2, 3), sortedSetOf("1", "2")) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/TruthyTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | 7 | class TruthyTest : StringSpec({ 8 | "yes/no values" { 9 | data class Foo(val a: Boolean, val b: Boolean, val c: Boolean, val d: Boolean, val e: Boolean, val f: Boolean) 10 | 11 | val config = ConfigLoader().loadConfigOrThrow("/truthy_yesno.json") 12 | config.a shouldBe true 13 | config.b shouldBe false 14 | config.c shouldBe true 15 | config.d shouldBe false 16 | config.e shouldBe true 17 | config.f shouldBe false 18 | } 19 | "1/0 values" { 20 | data class Foo(val a: Boolean, val b: Boolean) 21 | 22 | val config = ConfigLoader().loadConfigOrThrow("/truthy_10.json") 23 | config.a shouldBe true 24 | config.b shouldBe false 25 | } 26 | "T/F values" { 27 | data class Foo(val a: Boolean, val b: Boolean, val c: Boolean, val d: Boolean) 28 | 29 | val config = ConfigLoader().loadConfigOrThrow("/truthy_TF.json") 30 | config.a shouldBe true 31 | config.b shouldBe true 32 | config.c shouldBe false 33 | config.d shouldBe false 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /hoplite-json/src/test/kotlin/com/sksamuel/hoplite/json/UrlDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.json 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.net.URI 7 | import java.net.URL 8 | 9 | class UrlDecoderTest : StringSpec({ 10 | "Urls decoded from JSON" { 11 | data class Test(val a: URL, val b: URI) 12 | 13 | val config = ConfigLoader().loadConfigOrThrow("/test_urls.json") 14 | config shouldBe Test( 15 | URL("http://www.google.com?search=noprivacy"), 16 | URI.create("http://www.google.com?search=noprivacy") 17 | ) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "hello", 3 | "b": 1, 4 | "c": true, 5 | "d": 2.3 6 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/basic_with_comments.json: -------------------------------------------------------------------------------- 1 | { 2 | // The 'a' property 3 | "a": "hello", 4 | // The 'b' property 5 | "b": 1, 6 | // The 'c' property 7 | "c": true, 8 | // The 'd' property 9 | "d": 2.3 10 | } 11 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/empty.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sksamuel/hoplite/e211e07c369b40a7dc9a706f1f0d144a9c457266/hoplite-json/src/test/resources/empty.json -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/error1.json: -------------------------------------------------------------------------------- 1 | { 2 | "wrongType": 123, 3 | "notalist": true, 4 | "notaset": 123, 5 | "notamap": 6666.666, 6 | "notnull": null, 7 | "duration" : "10 grams", 8 | "season": "Fun", 9 | "nested": { 10 | "a": "qwqwe", 11 | "b": "qwqwe", 12 | "c": "qwqwe", 13 | "d": "qwqwe" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/nested_basic_arrays.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "hello", 3 | "b": [ 4 | "x", 5 | "y", 6 | "z" 7 | ] 8 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/nested_container_arrays.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "hello", 3 | "b": [ 4 | { 5 | "c": "hello", 6 | "d": true 7 | }, 8 | { 9 | "e": 1.4, 10 | "f": 4 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/principal.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sammy" 3 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/sealed_class_linked_hash_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "expressions": { 3 | "const1": { 4 | "number": 2 5 | }, 6 | "const2": { 7 | "number": 10 8 | }, 9 | "text1": { 10 | "str": "This is a example text." 11 | }, 12 | "const3": { 13 | "number": 42 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/sealed_test_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "host": "foo.local", 4 | "port": 1234, 5 | "user": "sammy" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/sealed_test_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/sealed_test_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": "Embedded" 3 | } 4 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/sets.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": [ 3 | 3, 4 | 1, 5 | 2, 6 | 3, 7 | 3 8 | ], 9 | "b": "1,1,2,2,2,1" 10 | } 11 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/size_in_bytes.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "10b", 3 | "b": "12Mb", 4 | "c": "55MiB", 5 | "d": "44gb" 6 | } 7 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/sysproptest1.props: -------------------------------------------------------------------------------- 1 | foo=x 2 | woo=y 3 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/sysproptest2.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": { 3 | "bar": { 4 | "s": 5, 5 | "t": 6 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/test_bigdecimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 10, 3 | "b": 20.3334, 4 | "c": "10.2", 5 | "d": "20.3334" 6 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/test_biginteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 10000, 3 | "b": "1231412412" 4 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/test_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file": "/home/user/sam" 3 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/test_ipnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "10.0.0.2" 3 | } 4 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/test_path.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "/home/user/sam" 3 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/test_urls.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "http://www.google.com?search=noprivacy", 3 | "b": "http://www.google.com?search=noprivacy" 4 | } 5 | -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/truthy_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "1", 3 | "b": "0" 4 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/truthy_TF.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "T", 3 | "b": "t", 4 | "c": "f", 5 | "d": "F" 6 | } -------------------------------------------------------------------------------- /hoplite-json/src/test/resources/truthy_yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "yes", 3 | "b": "NO", 4 | "c": "YES", 5 | "d": "no", 6 | "e": "true", 7 | "f": "false" 8 | } -------------------------------------------------------------------------------- /hoplite-micrometer-datadog/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.micrometer.registry.datadog) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-micrometer-datadog/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.micrometer.datadog.DatadogConfigDecoder 2 | -------------------------------------------------------------------------------- /hoplite-micrometer-prometheus/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.micrometer.registry.prometheus) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-micrometer-prometheus/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.micrometer.prometheus.PrometheusConfigDecoder 2 | -------------------------------------------------------------------------------- /hoplite-micrometer-statsd/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.micrometer.registry.statsd) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-micrometer-statsd/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.micrometer.statsd.StatsdConfigDecoder 2 | -------------------------------------------------------------------------------- /hoplite-toml/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.tomlj) 4 | } 5 | 6 | apply("../publish.gradle.kts") 7 | -------------------------------------------------------------------------------- /hoplite-toml/src/main/resources/META-INF/services/com.sksamuel.hoplite.parsers.Parser: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.toml.TomlParser -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/ConfigAliasNormalizationTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigAlias 4 | import com.sksamuel.hoplite.ConfigLoader 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class ConfigAliasNormalizationTest : FunSpec({ 9 | data class AliasConfig( 10 | @ConfigAlias("fooBarAlias") val fooBar: String, 11 | ) 12 | 13 | test("parsers should support @ConfigAlias with field normalization") { 14 | val config = ConfigLoader().loadConfigOrThrow("/alias_normalization.toml") 15 | config.fooBar shouldBe "Tom Preston-Werner" 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/ConfigAliasTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigAlias 4 | import com.sksamuel.hoplite.ConfigLoader 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class ConfigAliasTest : FunSpec({ 9 | data class A( 10 | @ConfigAlias("b") @ConfigAlias("zah") val bb: String, 11 | val c: Int 12 | ) 13 | 14 | data class D( 15 | val e: String, 16 | @ConfigAlias("f") val ff: Boolean 17 | ) 18 | 19 | data class AliasConfig( 20 | val a: A, 21 | @ConfigAlias("d") val dd: D 22 | ) 23 | 24 | test("parsers should support @ConfigAlias") { 25 | val config = ConfigLoader().loadConfigOrThrow("/alias.toml") 26 | config.a.bb shouldBe "Tom Preston-Werner" 27 | config.a.c shouldBe 5000 28 | config.dd.e shouldBe "192.168.1.1" 29 | config.dd.ff shouldBe true 30 | } 31 | 32 | test("parsers should support multiple @ConfigAlias") { 33 | val config = ConfigLoader().loadConfigOrThrow("/repeated_alias.toml") 34 | config.a.bb shouldBe "Tom Preston-Werner" 35 | config.a.c shouldBe 5000 36 | config.dd.e shouldBe "192.168.1.1" 37 | config.dd.ff shouldBe true 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/DataClassInitializationErrorTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import io.kotest.assertions.throwables.shouldThrowAny 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.should 7 | import io.kotest.matchers.string.contain 8 | 9 | // https://github.com/sksamuel/hoplite/issues/165 10 | class DataClassInitializationErrorTest : FunSpec() { 11 | init { 12 | test("an error when instantiating a class should be propagated") { 13 | shouldThrowAny { 14 | ConfigLoaderBuilder.default() 15 | .addSource(TomlPropertySource("a = 123123213123")) 16 | .build() 17 | .loadConfigOrThrow() 18 | }.message should contain("boom") 19 | } 20 | } 21 | } 22 | 23 | data class Foo(val a: String) { 24 | init { 25 | error("boom") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/DurationTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | import kotlin.time.Duration.Companion.milliseconds 7 | import kotlin.time.ExperimentalTime 8 | 9 | @OptIn(ExperimentalTime::class) 10 | class DurationTest : StringSpec() { 11 | init { 12 | 13 | "java durations should be parsable from milli longs" { 14 | data class Test(val a: java.time.Duration) 15 | ConfigLoader().loadConfigOrThrow("/duration_as_longs.toml").a shouldBe 16 | java.time.Duration.ofMillis(123123213123) 17 | } 18 | 19 | "kotlin durations should be parsable from milli longs" { 20 | data class Test(val a: kotlin.time.Duration) 21 | ConfigLoader().loadConfigOrThrow("/duration_as_longs.toml").a shouldBe 22 | 123123213123.milliseconds 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/InetAddressDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.net.InetAddress 7 | 8 | class InetAddressDecoderTest : StringSpec({ 9 | "InetAddress decoded from TOML" { 10 | data class Test(val inet: InetAddress) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_inet.toml") 13 | config shouldBe Test(InetAddress.getByName("10.0.0.2")) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/LocaleDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | import java.util.Locale 7 | 8 | class LocaleDecoderTest : StringSpec({ 9 | "Locale decoded from TOML" { 10 | data class Test(val a: Locale, val b: Locale) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_locale.toml") 13 | config.a shouldBe Locale.forLanguageTag("en_GB") 14 | config.b shouldBe Locale.forLanguageTag("en_GB") 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/MapTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.assertions.throwables.shouldNotThrowAny 5 | import io.kotest.core.spec.style.StringSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class MapTest : StringSpec({ 9 | "Map key contains dots" { 10 | data class CountEntry(val number: Int) 11 | data class Config(val count: Map) 12 | 13 | val config = shouldNotThrowAny { 14 | ConfigLoader().loadConfigOrThrow("/key_with_dots_in_map.toml") 15 | } 16 | config shouldBe Config( 17 | count = mapOf( 18 | "one" to CountEntry(1), 19 | "twenty.two" to CountEntry(22), 20 | "nine.one.one" to CountEntry(911), 21 | "one.hundred.and.ten" to CountEntry(110) 22 | ) 23 | ) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/PairDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | 7 | class PairDecoderTest : StringSpec({ 8 | "pair decoded from toml" { 9 | data class Test(val a: Pair, val b: Pair) 10 | 11 | val config = ConfigLoader().loadConfigOrThrow("/test_pair.toml") 12 | config shouldBe Test("hello" to "world", "5" to 6) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/PeriodKeyTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class PeriodKeyTest : FunSpec() { 8 | init { 9 | test("period in toml key should parseable") { 10 | val contents = """ 11 | value = 10 12 | [values] 13 | "cleanup.policy" = "compact" 14 | "delete.retention.ms" = "604800000 15 | """" 16 | ConfigLoader.builder() 17 | .addSource(TomlPropertySource(contents)) 18 | .build() 19 | .loadConfigOrThrow() 20 | .values shouldBe mapOf("cleanup.policy" to "compact", "delete.retention.ms" to "604800000") 21 | } 22 | } 23 | } 24 | 25 | data class Configuration( 26 | val value: Int = 5, 27 | val values: Map = mapOf(), 28 | ) 29 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/PeriodTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.time.Period 7 | 8 | class PeriodTest : StringSpec() { 9 | init { 10 | "periods happy path" { 11 | data class Test(val a: Period, 12 | val b: Period, 13 | val c: Period, 14 | val d: Period, 15 | val e: Period, 16 | val f: Period, 17 | val g: Period) 18 | ConfigLoader().loadConfigOrThrow("/period.toml") shouldBe Test( 19 | Period.ofDays(7), 20 | Period.ofDays(2), 21 | Period.ofYears(3), 22 | Period.ofMonths(4), 23 | Period.ofYears(3), 24 | Period.ofDays(63), 25 | Period.ofDays(7)) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/RegexDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | 7 | class RegexDecoderTest : StringSpec({ 8 | "regex decoded from TOML" { 9 | data class Test(val a: Regex) 10 | 11 | val config = ConfigLoader().loadConfigOrThrow("/test_regex.toml") 12 | config.a.toString() shouldBe ".*?".toRegex().toString() 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/SecretTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import com.sksamuel.hoplite.Secret 5 | import io.kotest.core.spec.style.StringSpec 6 | import io.kotest.matchers.shouldBe 7 | import io.kotest.matchers.types.shouldBeTypeOf 8 | 9 | class SecretTest : StringSpec() { 10 | init { 11 | "support built in Secret type" { 12 | data class Test(val a: Secret) 13 | 14 | val test = ConfigLoader().loadConfigOrThrow("/masked.toml") 15 | test.a.shouldBeTypeOf() 16 | test.a.value shouldBe "password" 17 | test.a.toString() shouldBe "****" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/SerializableClassTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.WordSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | data class NestedSerializableConfig(val a: String, val b: Boolean) 8 | 9 | data class SerializableConfig(val l: Long, val d: Double, val nested: NestedSerializableConfig) 10 | 11 | class SerializableClassTest : WordSpec() { 12 | init { 13 | "A class marked with @Serializable" should { 14 | "be usable as a config class" { 15 | ConfigLoader().loadConfigOrThrow("/serializable.toml") shouldBe 16 | SerializableConfig(l = 123, d = 451.23, nested = NestedSerializableConfig(a = "foo", b = true)) 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/UrlDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.net.URI 7 | import java.net.URL 8 | 9 | class UrlDecoderTest : StringSpec({ 10 | "Urls decoded from TOML" { 11 | data class Test(val a: URL, val b: URI) 12 | 13 | val config = ConfigLoader().loadConfigOrThrow("/test_urls.toml") 14 | config shouldBe Test( 15 | URL("http://www.google.com?search=noprivacy"), 16 | URI.create("http://www.google.com?search=noprivacy") 17 | ) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/kotlin/com/sksamuel/hoplite/toml/ValueTypeTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.toml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | @JvmInline 8 | value class ValueType(val value: String) 9 | data class ValueConfig(val foo: ValueType) 10 | 11 | class ValueTypeTest : FunSpec() { 12 | init { 13 | test("should decode value types") { 14 | ConfigLoader().loadConfigOrThrow("/value.toml").foo shouldBe ValueType("bar") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/alias.toml: -------------------------------------------------------------------------------- 1 | foo = "bar" 2 | 3 | [a] 4 | b = "Tom Preston-Werner" 5 | c = 5000 6 | 7 | [d] 8 | e = "192.168.1.1" 9 | f = true 10 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/alias_normalization.toml: -------------------------------------------------------------------------------- 1 | fooBarAlias = "Tom Preston-Werner" 2 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/basic.toml: -------------------------------------------------------------------------------- 1 | title = "TOML Example" 2 | 3 | [owner] 4 | name = "Tom Preston-Werner" 5 | dob = 1979-05-27T07:32:00-08:00 # First class dates 6 | 7 | [database] 8 | server = "192.168.1.1" 9 | ports = [ 8001, 8001, 8002 ] 10 | connection_max = 5000 11 | enabled = true 12 | 13 | [servers] 14 | 15 | # Indentation (tabs and/or spaces) is allowed but not required 16 | [servers.alpha] 17 | ip = "10.0.0.1" 18 | dc = "eqdc10" 19 | 20 | [servers.beta] 21 | ip = "10.0.0.2" 22 | dc = "eqdc10" 23 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/duration_as_longs.toml: -------------------------------------------------------------------------------- 1 | a = 123123213123 2 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/key_with_dots_in_map.toml: -------------------------------------------------------------------------------- 1 | [count.one] 2 | number = 1 3 | 4 | [count."twenty.two"] 5 | number = 22 6 | 7 | [count."nine.one.one"] 8 | number = 911 9 | 10 | [count."one.hundred.and.ten"] 11 | number = 110 12 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/masked.toml: -------------------------------------------------------------------------------- 1 | a = "password" 2 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/period.toml: -------------------------------------------------------------------------------- 1 | a = "1 week" 2 | b = "2 days" 3 | c = "3 years" 4 | d = "4m" 5 | e = "3y" 6 | f = "9w" 7 | g = "7d" 8 | 9 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/repeated_alias.toml: -------------------------------------------------------------------------------- 1 | foo = "bar" 2 | 3 | [a] 4 | zah = "Tom Preston-Werner" 5 | c = 5000 6 | 7 | [d] 8 | e = "192.168.1.1" 9 | f = true 10 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/serializable.toml: -------------------------------------------------------------------------------- 1 | l = "123" 2 | d = 451.23 3 | 4 | [nested] 5 | a = "foo" 6 | b = true 7 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/test_inet.toml: -------------------------------------------------------------------------------- 1 | inet = "10.0.0.2" 2 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/test_locale.toml: -------------------------------------------------------------------------------- 1 | a = "en_GB" 2 | b = "es_US" 3 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/test_pair.toml: -------------------------------------------------------------------------------- 1 | a = [ "hello", "world"] 2 | b = [ 5, 6 ] 3 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/test_regex.toml: -------------------------------------------------------------------------------- 1 | a = ".*?" 2 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/test_urls.toml: -------------------------------------------------------------------------------- 1 | a = "http://www.google.com?search=noprivacy" 2 | b = "http://www.google.com?search=noprivacy" 3 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/unused.toml: -------------------------------------------------------------------------------- 1 | title = "TOML Example" 2 | 3 | [owner] 4 | name = "Tom Preston-Werner" 5 | dob = 1979-05-27T07:32:00-08:00 # First class dates 6 | 7 | [database] 8 | server = "192.168.1.1" 9 | connection_max = 5000 10 | enabled = true 11 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/value.toml: -------------------------------------------------------------------------------- 1 | foo = "bar" 2 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | version = "1.2.3" 3 | strict_version = { strictly = "2.3.4" } 4 | -------------------------------------------------------------------------------- /hoplite-toml/src/test/resources/versions2.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | version = "1.2.3" 3 | strict_version = { boo = "2.3.4" } 4 | -------------------------------------------------------------------------------- /hoplite-vault/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | implementation(libs.spring.vault.core) 4 | testApi(libs.kotest.extensions.testcontainers) 5 | testApi(libs.testcontainers.vault) 6 | } 7 | 8 | apply("../publish.gradle.kts") 9 | -------------------------------------------------------------------------------- /hoplite-vavr/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | testImplementation(projects.hopliteToml) 4 | testImplementation(projects.hopliteYaml) 5 | testImplementation(projects.hopliteJson) 6 | implementation(libs.vavr.kotlin) 7 | } 8 | 9 | apply("../publish.gradle.kts") 10 | -------------------------------------------------------------------------------- /hoplite-vavr/src/main/kotlin/com/sksamuel/hoplite/decoder/vavr/OptionDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder.vavr 2 | 3 | import com.sksamuel.hoplite.* 4 | import com.sksamuel.hoplite.decoder.Decoder 5 | import com.sksamuel.hoplite.fp.flatMap 6 | import com.sksamuel.hoplite.fp.invalid 7 | import com.sksamuel.hoplite.fp.valid 8 | import io.vavr.control.Option 9 | import io.vavr.kotlin.none 10 | import io.vavr.kotlin.some 11 | import kotlin.reflect.KType 12 | 13 | class OptionDecoder : Decoder> { 14 | 15 | override fun supports(type: KType): Boolean = type.classifier == Option::class 16 | 17 | override fun decode(node: Node, type: KType, context: DecoderContext): ConfigResult> { 18 | require(type.arguments.size == 1) 19 | 20 | val t = type.arguments[0].type!! 21 | 22 | fun decode(value: Node, decoder: Decoder): ConfigResult> = 23 | decoder.decode(value, t, context).map { some(it) } 24 | 25 | return context.decoder(t).flatMap { decoder -> 26 | when (node) { 27 | is Undefined -> none>().valid() 28 | is NullNode -> none>().valid() 29 | is StringNode, is LongNode, is DoubleNode, is BooleanNode -> decode(node, decoder) 30 | else -> ConfigFailure.DecodeError(node, type).invalid() 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hoplite-vavr/src/main/resources/META-INF/services/com.sksamuel.hoplite.decoder.Decoder: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.decoder.vavr.MapDecoder 2 | com.sksamuel.hoplite.decoder.vavr.ListDecoder 3 | com.sksamuel.hoplite.decoder.vavr.SortedSetDecoder 4 | com.sksamuel.hoplite.decoder.vavr.SetDecoder 5 | com.sksamuel.hoplite.decoder.vavr.OptionDecoder 6 | com.sksamuel.hoplite.decoder.vavr.Tuple2Decoder 7 | com.sksamuel.hoplite.decoder.vavr.Tuple3Decoder 8 | com.sksamuel.hoplite.decoder.vavr.Tuple4Decoder 9 | com.sksamuel.hoplite.decoder.vavr.Tuple5Decoder 10 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/kotlin/com/sksamuel/hoplite/decoder/vavr/ListDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder.vavr 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import io.vavr.collection.List 7 | import io.vavr.kotlin.list 8 | 9 | class ListDecoderTest : FunSpec({ 10 | 11 | test("List decoded from yaml") { 12 | data class Test(val strings: List) 13 | 14 | val config = ConfigLoader().loadConfigOrThrow("/test_list.yml") 15 | config shouldBe Test(list("test1", "test2", "test3")) 16 | } 17 | 18 | }) 19 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/kotlin/com/sksamuel/hoplite/decoder/vavr/MapDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder.vavr 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import com.sksamuel.hoplite.sources.EnvironmentVariablesPropertySource 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | import io.vavr.collection.Map 8 | import io.vavr.kotlin.linkedHashMap 9 | 10 | class MapDecoderTest : FunSpec({ 11 | data class Test(val map: Map) 12 | 13 | test("Map decoded from yaml") { 14 | val config = ConfigLoader().loadConfigOrThrow("/test_map.yml") 15 | config shouldBe Test(linkedHashMap("key1" to "test1", "key2" to "test2", "key-3" to "test3", "Key4" to "test4")) 16 | } 17 | 18 | test("Map decoded from environment") { 19 | run { 20 | ConfigLoader { 21 | addPropertySource(EnvironmentVariablesPropertySource( 22 | environmentVariableMap = { 23 | mapOf( 24 | "map_key1" to "test1", 25 | "map_key2" to "test2", 26 | "map_key-3" to "test3", 27 | "map_Key4" to "test4", 28 | ) 29 | } 30 | )) 31 | }.loadConfigOrThrow() 32 | } shouldBe Test(linkedHashMap("key1" to "test1", "key2" to "test2", "key-3" to "test3", "Key4" to "test4")) 33 | } 34 | 35 | }) 36 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/kotlin/com/sksamuel/hoplite/decoder/vavr/OptionDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder.vavr 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import io.vavr.control.Option 7 | import io.vavr.kotlin.none 8 | import io.vavr.kotlin.some 9 | 10 | class OptionDecoderTest : FunSpec({ 11 | 12 | test("Options decoded from yaml") { 13 | data class Test(val a: Option, 14 | val b: Option, 15 | val c: Option) 16 | 17 | val config = ConfigLoader().loadConfigOrThrow("/options.yml") 18 | config.a shouldBe none() 19 | config.b shouldBe some("hello") 20 | config.c shouldBe some(123) 21 | } 22 | 23 | }) 24 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/kotlin/com/sksamuel/hoplite/decoder/vavr/SetDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder.vavr 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import io.vavr.collection.Set 7 | import io.vavr.kotlin.linkedHashSet 8 | 9 | class SetDecoderTest : FunSpec({ 10 | 11 | test("Set decoded from yaml") { 12 | data class Test(val numbers: Set) 13 | 14 | val config = ConfigLoader().loadConfigOrThrow("/test_set.yml") 15 | config shouldBe Test(linkedHashSet(1, 2, 3)) 16 | } 17 | 18 | }) 19 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/kotlin/com/sksamuel/hoplite/decoder/vavr/SortedSetDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder.vavr 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import io.vavr.collection.SortedSet 7 | import io.vavr.kotlin.treeSet 8 | 9 | class SortedSetDecoderTest : FunSpec({ 10 | 11 | test("SortedSet decoded from yaml") { 12 | data class Test(val numbers: SortedSet) 13 | 14 | val config = ConfigLoader().loadConfigOrThrow("/test_set.yml") 15 | config shouldBe Test(treeSet(3, 2, 1)) 16 | } 17 | 18 | }) 19 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/resources/options.yml: -------------------------------------------------------------------------------- 1 | a: null 2 | b: "hello" 3 | c: 123 4 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/resources/test_list.yml: -------------------------------------------------------------------------------- 1 | strings: test1, test2, test3 2 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/resources/test_map.yml: -------------------------------------------------------------------------------- 1 | map: 2 | key1: "test1" 3 | key2: "test2" 4 | key-3: "test3" 5 | Key4: "test4" 6 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/resources/test_set.yml: -------------------------------------------------------------------------------- 1 | numbers: 1, 2, 3 2 | -------------------------------------------------------------------------------- /hoplite-vavr/src/test/resources/test_tuples.yml: -------------------------------------------------------------------------------- 1 | tuple2: ["test1", "test2"] 2 | tuple3: ["test1", 1, "test2"] 3 | tuple4: [1, 2, 3, 4] 4 | tuple5: [true, "test", 1, 2, "3c7799c0-3e3a-11eb-b378-0242ac130002"] 5 | -------------------------------------------------------------------------------- /hoplite-watch-consul/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | api(projects.hopliteWatch) 4 | implementation(libs.consul.client) 5 | 6 | testImplementation(libs.embedded.consul) 7 | testImplementation(projects.hopliteConsul) 8 | testImplementation(projects.hopliteJson) 9 | testImplementation(projects.hopliteYaml) 10 | } 11 | 12 | apply("../publish.gradle.kts") 13 | -------------------------------------------------------------------------------- /hoplite-watch-consul/src/main/kotlin/com/sksamuel/hoplite/watcher/consul/consul.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.watcher.consul 2 | 3 | import com.orbitz.consul.KeyValueClient 4 | import com.orbitz.consul.cache.KVCache 5 | import com.sksamuel.hoplite.watch.Watchable 6 | 7 | class ConsulWatcher(private val kvClient: KeyValueClient, private val paths: List) : Watchable { 8 | companion object { 9 | const val WatchSeconds = 3 10 | } 11 | override fun watch(callback: () -> Unit, errorHandler: (Throwable) -> Unit) { 12 | paths.forEach { 13 | val cache = KVCache.newCache(kvClient, it, WatchSeconds) 14 | cache.addListener { callback() } 15 | cache.start() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hoplite-watch-consul/src/test/resources/consulConfig.yml: -------------------------------------------------------------------------------- 1 | foo: ${consul:foo} 2 | -------------------------------------------------------------------------------- /hoplite-watch/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.hopliteCore) 3 | // implementation(KotlinX.coroutines.core) 4 | testImplementation(projects.hopliteJson) 5 | } 6 | 7 | apply("../publish.gradle.kts") 8 | -------------------------------------------------------------------------------- /hoplite-watch/src/main/kotlin/com/sksamuel/hoplite/watch/Watchable.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.watch 2 | 3 | import kotlinx.coroutines.DelicateCoroutinesApi 4 | import kotlinx.coroutines.GlobalScope 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.isActive 7 | import kotlinx.coroutines.launch 8 | 9 | interface Watchable { 10 | fun watch(callback: () -> Unit, errorHandler: (Throwable) -> Unit) 11 | } 12 | 13 | @OptIn(DelicateCoroutinesApi::class) 14 | class FixedIntervalWatchable(private val intervalMs: Long) : Watchable { 15 | override fun watch(callback: () -> Unit, errorHandler: (Throwable) -> Unit) { 16 | GlobalScope.launch { 17 | while (isActive) { 18 | delay(intervalMs) 19 | callback() 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hoplite-yaml/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.serialization) 3 | } 4 | 5 | dependencies { 6 | api(projects.hopliteCore) 7 | implementation(libs.snakeyaml) 8 | } 9 | 10 | apply("../publish.gradle.kts") 11 | -------------------------------------------------------------------------------- /hoplite-yaml/src/main/kotlin/com/sksamuel/hoplite/yaml/YamlPropertySource.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigResult 4 | import com.sksamuel.hoplite.Node 5 | import com.sksamuel.hoplite.PropertySource 6 | import com.sksamuel.hoplite.PropertySourceContext 7 | import com.sksamuel.hoplite.fp.valid 8 | import java.util.concurrent.atomic.AtomicInteger 9 | 10 | private val counter = AtomicInteger(0) 11 | 12 | /** 13 | * A [PropertySource] that provides values via a given yaml string. 14 | * For example: 15 | * 16 | * YamlPropertySource( 17 | * """ 18 | * a: 19 | * name: Sam 20 | * v: 21 | * city: Chicago 22 | * """) 23 | */ 24 | class YamlPropertySource( 25 | private val str: String, 26 | private val source: String = "Provided YAML ${counter.incrementAndGet()}" 27 | ) : PropertySource { 28 | 29 | override fun source(): String = source 30 | 31 | override fun node(context: PropertySourceContext): ConfigResult = 32 | YamlParser().load(str.byteInputStream(), source).valid().apply { 33 | println(this) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /hoplite-yaml/src/main/resources/META-INF/services/com.sksamuel.hoplite.parsers.Parser: -------------------------------------------------------------------------------- 1 | com.sksamuel.hoplite.yaml.YamlParser -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/Base64DecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigException 4 | import com.sksamuel.hoplite.ConfigLoader 5 | import com.sksamuel.hoplite.ConfigLoaderBuilder 6 | import com.sksamuel.hoplite.yaml.YamlPropertySource 7 | import io.kotest.assertions.throwables.shouldThrow 8 | import io.kotest.core.spec.style.StringSpec 9 | import io.kotest.matchers.shouldBe 10 | import io.kotest.matchers.string.shouldContain 11 | import java.nio.ByteBuffer 12 | 13 | class Base64DecoderTest : StringSpec({ 14 | 15 | "base64 decoded from string" { 16 | 17 | data class Test(val b: Base64) 18 | 19 | val config = ConfigLoader().loadConfigOrThrow("/base64.yml") 20 | config shouldBe Test(Base64(ByteBuffer.wrap("hello world".encodeToByteArray()))) 21 | } 22 | 23 | "invalid base64" { 24 | 25 | data class Test(val b: Base64) 26 | 27 | shouldThrow { 28 | ConfigLoaderBuilder.default() 29 | .addSource(YamlPropertySource("b: 123!")) 30 | .build() 31 | .loadConfigOrThrow() 32 | }.message shouldContain "Base64 could not be decoded from a String value: 123!" 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/FileDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | import java.io.File 7 | 8 | class FileDecoderTest : StringSpec({ 9 | "file decoded from yaml" { 10 | data class Test(val file: File) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_file.yml") 13 | config shouldBe Test(File("/home/user/sam")) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/InetAddressDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.net.InetAddress 7 | 8 | class InetAddressDecoderTest : StringSpec({ 9 | "InetAddress decoded from yaml" { 10 | data class Test(val a: InetAddress) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/test_ipnet.yml") 13 | config shouldBe Test(InetAddress.getByName("10.0.0.2")) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/NullOverrideTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.fp.valid 5 | import com.sksamuel.hoplite.yaml.YamlPropertySource 6 | import io.kotest.core.spec.style.FunSpec 7 | import io.kotest.matchers.shouldBe 8 | 9 | class NullOverrideTest : FunSpec() { 10 | init { 11 | test("ability to set value to null explicitly #377") { 12 | ConfigLoaderBuilder.default() 13 | .addPropertySource(YamlPropertySource("username: null")) 14 | .addPropertySource(YamlPropertySource("username: something")) 15 | .allowNullOverride() 16 | .build() 17 | .loadConfig() shouldBe Config(null).valid() 18 | } 19 | } 20 | } 21 | 22 | data class Config(val username: String?) 23 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/PathDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | import java.nio.file.Path 7 | import java.nio.file.Paths 8 | 9 | class PathDecoderTest : StringSpec({ 10 | "Path decoded from yaml" { 11 | data class Test(val path: Path) 12 | 13 | val config = ConfigLoaderBuilder.defaultWithoutPropertySources().build() 14 | .loadConfigOrThrow("/test_path.yml") 15 | config shouldBe Test(Paths.get("/home/user/sam")) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/PrincipalDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.security.Principal 7 | 8 | class PrincipalDecoderTest : StringSpec({ 9 | "Principal decoded from yaml" { 10 | data class Test(val name: Principal) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/principal.yaml") 13 | config shouldBe Test(BasicPrincipal("sammy")) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/PropsDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.addResourceSource 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | import java.util.Properties 8 | 9 | class PropsDecoderTest : FunSpec({ 10 | test("props should be supported as a nested type") { 11 | data class Config(val myprops: Properties) 12 | 13 | val expected = Properties() 14 | expected["foo"] = "wibble" 15 | expected["bar"] = "wobble" 16 | 17 | ConfigLoaderBuilder.default() 18 | .addResourceSource("/props_decoder.yml") 19 | .build() 20 | .loadConfig().getUnsafe() shouldBe Config(expected) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/StringSquashTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.ExperimentalHoplite 5 | import io.kotest.assertions.throwables.shouldThrowAny 6 | import io.kotest.core.spec.style.FunSpec 7 | import io.kotest.matchers.shouldBe 8 | import io.kotest.matchers.string.shouldContain 9 | 10 | @OptIn(ExperimentalHoplite::class) 11 | class StringSquashTest : FunSpec() { 12 | init { 13 | test("should squash arrays to a string when enabled") { 14 | data class Config(val a: String) 15 | ConfigLoaderBuilder.default() 16 | .flattenArraysToString() 17 | .build() 18 | .loadConfigOrThrow("/array.yml") 19 | .a shouldBe "1,2,3,4,3,2,1" 20 | } 21 | 22 | test("should NOT squash arrays to a string when not enabled") { 23 | data class Config(val a: String) 24 | shouldThrowAny { 25 | ConfigLoaderBuilder.default() 26 | .build() 27 | .loadConfigOrThrow("/array.yml") 28 | }.message shouldContain "Required type String could not be decoded from a List" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/UrlDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.net.URI 7 | import java.net.URL 8 | 9 | class UrlDecoderTest : StringSpec({ 10 | "Urls decoded from YAML" { 11 | data class Test(val a: URL, val b: URI) 12 | 13 | val config = ConfigLoader().loadConfigOrThrow("/test_urls.yml") 14 | config shouldBe Test( 15 | URL("http://www.google.com?search=noprivacy"), 16 | URI.create("http://www.google.com?search=noprivacy") 17 | ) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/decoder/ValueTypeDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.decoder 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class ValueTypeDecoderTest : StringSpec({ 8 | "single value field as value type" { 9 | 10 | // host.value doesn't exist in the config, but because it is a value type, the value of "host" 11 | // will be used instead 12 | data class Host(val value: String) 13 | data class Port(val value: Int) 14 | data class Server(val host: Host, val port: Port) 15 | data class Config(val server: Server) 16 | 17 | val config = ConfigLoader().loadConfigOrThrow("/value_type.yml") 18 | config shouldBe Config(Server(Host("localhost"), Port(1234))) 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/AnchorAliasTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class AnchorAliasTest : FunSpec() { 8 | init { 9 | 10 | test("should support anchors and aliases") { 11 | data class Hand(val name: String, val weight: Int) 12 | data class Test(val objects: List, val lefthand: Hand, val righthand: Hand) 13 | 14 | val config = ConfigLoader().loadConfigOrThrow( 15 | "/anchor.yml", 16 | "/anchor-merge-base.yml" 17 | ) 18 | 19 | config shouldBe Test( 20 | listOf("Apple", "Beachball", "Cartoon", "Duckface", "Apple"), 21 | Hand("The Bastard Sword of Eowyn", 30), 22 | Hand("The Bastard Sword of Eowyn", 30) 23 | ) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/DenormalizedLinkedHashMapKeysTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.addResourceOrFileSource 5 | import io.kotest.core.spec.style.FunSpec 6 | import io.kotest.matchers.shouldBe 7 | 8 | class DenormalizedLinkedHashMapKeysTest : FunSpec({ 9 | data class Foo( 10 | val xVal: String = "x" 11 | ) 12 | 13 | data class LinkedHashMapContainer( 14 | val m: LinkedHashMap = linkedMapOf() 15 | ) 16 | 17 | test("should set denormalized map keys and decode a data class inside a linked has map map") { 18 | val config = ConfigLoaderBuilder.default() 19 | .addResourceOrFileSource("/test_data_class_in_map.yaml") 20 | .build() 21 | .loadConfigOrThrow() 22 | 23 | config shouldBe LinkedHashMapContainer( 24 | m = linkedMapOf( 25 | "DC1" to Foo("10"), 26 | "DC2" to Foo("20"), 27 | ) 28 | ) 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/GithubIssue72.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | data class EngineConfig( 8 | val env: String, 9 | val redis: RedisConfig, 10 | val odb: OdbConfig) 11 | 12 | data class RedisConfig( 13 | val url: String, 14 | val requestChannel: String, 15 | val responseChannel: String) 16 | 17 | data class OdbConfig( 18 | val home: String, 19 | val configFile: String, 20 | val dbFile: String, 21 | val server: Boolean) 22 | 23 | class GithubIssue72 : StringSpec() { 24 | init { 25 | "NoSuchFieldError: Companion #72" { 26 | ConfigLoader().loadConfigOrThrow("/github72.yml") shouldBe 27 | EngineConfig( 28 | env = "production", 29 | redis = RedisConfig(url = "redis://localhost:6379/", requestChannel = "foo", responseChannel = "foo"), 30 | odb = OdbConfig(home = "db", configFile = "config/production.conf", dbFile = "engine.db", server = false) 31 | ) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/InputStreamPropertySourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.sources.InputStreamPropertySource 5 | import io.kotest.core.spec.style.StringSpec 6 | import io.kotest.matchers.shouldBe 7 | import io.kotest.matchers.types.shouldBeInstanceOf 8 | 9 | class InputStreamPropertySourceTest : StringSpec({ 10 | "input stream decoding test" { 11 | 12 | data class Test(val a: Set, val b: Set) 13 | 14 | val stream = javaClass.getResourceAsStream("/sets.yml") 15 | val config = ConfigLoaderBuilder.default() 16 | .addPropertySource(InputStreamPropertySource(stream, "yml", "yml input stream")) 17 | .build() 18 | .loadConfigOrThrow() 19 | 20 | config shouldBe Test(setOf(1, 2, 3), setOf("1", "2")) 21 | config.a.shouldBeInstanceOf>() 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/InstantTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.time.Instant 7 | 8 | 9 | class InstantTest : StringSpec({ 10 | "java.time.Instant decoded from YAML" { 11 | data class Config(val instant: Instant) 12 | 13 | val config = ConfigLoader().loadConfigOrThrow("/test_instant.yml") 14 | config shouldBe Config(Instant.parse("2015-03-14T09:26:53.590Z")) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/Issue236Test.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import io.kotest.core.spec.style.FreeSpec 5 | 6 | data class A( 7 | val foo: Fooby = Fooby() 8 | ) 9 | 10 | data class Fooby( 11 | val x: String = "x" 12 | ) 13 | 14 | class Issue236Test : FreeSpec({ 15 | 16 | val config = "{" + 17 | "}" 18 | 19 | "Bug Reproduce" { 20 | ConfigLoaderBuilder.default() 21 | .addSource(YamlPropertySource(config)) 22 | .build() 23 | .loadConfigOrThrow() 24 | } 25 | 26 | }) 27 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/KebabCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class KebabCaseTest : StringSpec({ 8 | 9 | "should support trailing numbers as components" { 10 | data class Foo( 11 | val myServer1: String, 12 | val myServer2: String, 13 | val myServer11: String, 14 | val myServer12: String 15 | ) 16 | ConfigLoader.builder() 17 | .addSource(YamlPropertySource("my-server-1: a")) 18 | .addSource(YamlPropertySource("my-server2: b")) 19 | .addSource(YamlPropertySource("my-server-11: c")) 20 | .addSource(YamlPropertySource("my-server12: d")) 21 | .build() 22 | .loadConfigOrThrow() shouldBe Foo(myServer1 = "a", myServer2 = "b", myServer11 = "c", myServer12 = "d") 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/LinkedHashMapTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | 7 | class LinkedHashMapTest : StringSpec({ 8 | "LinkedHashMap decoded from yaml" { 9 | data class Test(val a: LinkedHashMap) 10 | 11 | val config = ConfigLoader().loadConfigOrThrow("/linked_hash_map.yml") 12 | config shouldBe Test(linkedMapOf("x" to "y", "z" to "c", "e" to "f", "g" to "h", "u" to "i")) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/MapTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class MapTest : FunSpec() { 8 | init { 9 | test("allow maps to be decoded from lists of two values of key/value pairs") { 10 | data class Test(val a: Map) 11 | 12 | val config = ConfigLoaderBuilder 13 | .default() 14 | .addPropertySource(YamlPropertySource( 15 | """ 16 | a: 17 | - key: arnold 18 | value: rimmer 19 | - key: mr 20 | value: flibble 21 | """ 22 | )) 23 | .build() 24 | .loadConfigOrThrow() 25 | config shouldBe Test(mapOf("arnold" to "rimmer", "mr" to "flibble")) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/MonthDayTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.matchers.shouldBe 5 | import io.kotest.core.spec.style.StringSpec 6 | import java.time.MonthDay 7 | 8 | class MonthDayTest : StringSpec({ 9 | "MonthDay decoded from YAML" { 10 | data class Test(val a: MonthDay, val b: MonthDay) 11 | 12 | val config = ConfigLoader().loadConfigOrThrow("/month_day.yml") 13 | config shouldBe Test(MonthDay.of(12, 30), MonthDay.of(4, 15)) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/NestedClassTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.shouldBe 6 | import java.time.LocalDate 7 | 8 | class NestedClassTest : FunSpec({ 9 | 10 | test("support two-level nested data classes") { 11 | data class Inner(val a: String, val b: String) 12 | data class Outer(val a: String, val b: Inner, val c: String) 13 | 14 | val config = ConfigLoader().loadConfigOrThrow("/test_nested2.yml") 15 | config shouldBe Outer("foo", Inner("goo", "moo"), "voo") 16 | } 17 | 18 | test("support three-level nested data classes") { 19 | data class Inner(val k: Int, val j: LocalDate) 20 | data class Middle(val a: Inner, val b: Double, val c: Long) 21 | data class Outer(val x: String, val y: Boolean, val z: Middle) 22 | 23 | val config = ConfigLoader().loadConfigOrThrow("/test_nested3.yml") 24 | config shouldBe Outer("foo", true, Middle(Inner(12, LocalDate.of(2016, 5, 12)), 7924.3, 91894781923)) 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/ParseErrorTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoaderBuilder 4 | import com.sksamuel.hoplite.PropertySource 5 | import io.kotest.assertions.throwables.shouldThrowAny 6 | import io.kotest.core.spec.style.FunSpec 7 | import io.kotest.matchers.string.shouldInclude 8 | 9 | class ParseErrorTest : FunSpec() { 10 | init { 11 | test("foo") { 12 | shouldThrowAny { 13 | ConfigLoaderBuilder.default() 14 | .addSource(PropertySource.resource("/bad_indent.yml")) 15 | .build() 16 | .loadNodeOrThrow() 17 | }.message shouldInclude """while parsing a block mapping 18 | in 'reader', line 22, column 5""" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/RandomPreprocessorTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.matchers.ints.shouldBePositive 6 | import io.kotest.matchers.shouldBe 7 | import io.kotest.matchers.string.shouldHaveLength 8 | 9 | class RandomPreprocessorTest : FunSpec() { 10 | 11 | data class Test( 12 | val c: Int, 13 | val d: Boolean, 14 | val e: String, 15 | val f: String, 16 | val g: String, 17 | val h: Double, 18 | val i: String, 19 | val uuid: String 20 | ) 21 | 22 | init { 23 | test("replace env vars") { 24 | for (k in 1..100) { 25 | val test = ConfigLoader().loadConfigOrThrow("/test_random_preprocessor.yml") 26 | test.c.shouldBePositive() 27 | test.e.length shouldBe 1 28 | test.f.length shouldBe 3 29 | test.i.shouldHaveLength(20) 30 | test.uuid.shouldHaveLength(36) 31 | } 32 | 33 | (1..100).map { 34 | ConfigLoader().loadConfigOrThrow("/test_random_preprocessor.yml").d 35 | }.toSet() shouldBe setOf(true, false) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/SetDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import com.sksamuel.hoplite.decoder.Wine 5 | import io.kotest.matchers.types.shouldBeInstanceOf 6 | import io.kotest.matchers.shouldBe 7 | import io.kotest.core.spec.style.StringSpec 8 | 9 | class SetDecoderTest : StringSpec({ 10 | 11 | "Set decoded from yml file" { 12 | data class Test(val a: Set, val b: Set) 13 | 14 | val config = ConfigLoader().loadConfigOrThrow("/sets.yml") 15 | config shouldBe Test(setOf(1, 2, 3), setOf("1", "2")) 16 | config.a.shouldBeInstanceOf>() 17 | } 18 | 19 | "Set of Kotlin.Enum decoded from yml file" { 20 | data class TestEnum(val wines: Set) 21 | 22 | val config = ConfigLoader().loadConfigOrThrow("/test_set_enum.yml") 23 | config shouldBe TestEnum(setOf(Wine.Malbec, Wine.Shiraz)) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/SnakeCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class SnakeCaseTest : StringSpec({ 8 | 9 | "testing config with a mix of snake and camel case" { 10 | data class Foo( 11 | val dripDrop: String, 12 | val tipTap: Boolean, 13 | val doubleTrouble: List, 14 | val wibbleWobble: Double 15 | ) 16 | ConfigLoader().loadConfigOrThrow("/snake_case.yml") shouldBe 17 | Foo(dripDrop = "hello", tipTap = true, doubleTrouble = listOf("1", "2", "3"), wibbleWobble = 124.55) 18 | } 19 | 20 | "should support trailing numbers as components" { 21 | data class Foo( 22 | val myServer1: String, 23 | val myServer2: String, 24 | val myServer11: String, 25 | val myServer12: String 26 | ) 27 | ConfigLoader.builder() 28 | .addSource(YamlPropertySource("my_server_1: a")) 29 | .addSource(YamlPropertySource("my_server2: b")) 30 | .addSource(YamlPropertySource("my_server_11: c")) 31 | .addSource(YamlPropertySource("my_server12: d")) 32 | .build() 33 | .loadConfigOrThrow() shouldBe Foo(myServer1 = "a", myServer2 = "b", myServer11 = "c", myServer12 = "d") 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/SysPropPreprocessorTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.FunSpec 5 | import io.kotest.extensions.system.withSystemProperty 6 | import io.kotest.matchers.shouldBe 7 | 8 | class SysPropPreprocessorTest : FunSpec() { 9 | 10 | data class Test( 11 | val a: String, 12 | val b: String, 13 | val c: String, 14 | val d: String, 15 | val e: String, 16 | val f: String 17 | ) 18 | 19 | init { 20 | test("replace placeholders with system properties") { 21 | withSystemProperty("wibble", "wobble") { 22 | ConfigLoader().loadConfigOrThrow("/test_sysproperty_replacement.yml") shouldBe 23 | Test( 24 | a = "foo", 25 | b = "wobble", 26 | c = "aawobble", 27 | d = "wobblebb", 28 | e = "aawobblebb", 29 | f = "\$wibble" 30 | ) 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/kotlin/com/sksamuel/hoplite/yaml/TruthyTest.kt: -------------------------------------------------------------------------------- 1 | package com.sksamuel.hoplite.yaml 2 | 3 | import com.sksamuel.hoplite.ConfigLoader 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.matchers.shouldBe 6 | 7 | class TruthyTest : StringSpec({ 8 | "yes/no values" { 9 | data class Foo(val a: Boolean, val b: Boolean, val c: Boolean, val d: Boolean, val e: Boolean, val f: Boolean) 10 | 11 | val config = ConfigLoader().loadConfigOrThrow("/truthy_yesno.yml") 12 | config.a shouldBe true 13 | config.b shouldBe false 14 | config.c shouldBe true 15 | config.d shouldBe false 16 | config.e shouldBe true 17 | config.f shouldBe false 18 | } 19 | "1/0 values" { 20 | data class Foo(val a: Boolean, val b: Boolean) 21 | 22 | val config = ConfigLoader().loadConfigOrThrow("/truthy_10.yml") 23 | config.a shouldBe true 24 | config.b shouldBe false 25 | } 26 | "T/F values" { 27 | data class Foo(val a: Boolean, val b: Boolean, val c: Boolean, val d: Boolean) 28 | 29 | val config = ConfigLoader().loadConfigOrThrow("/truthy_TF.yml") 30 | config.a shouldBe true 31 | config.b shouldBe true 32 | config.c shouldBe false 33 | config.d shouldBe false 34 | } 35 | }) 36 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/anchor-merge-base.yml: -------------------------------------------------------------------------------- 1 | objects: 2 | - Beachball 3 | 4 | lefthand: 5 | name: Anduril, the Sword of Aragon 6 | weight: 40 7 | 8 | righthand: 9 | name: Anduril, the Sword of Aragon 10 | weight: 40 11 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/anchor.yml: -------------------------------------------------------------------------------- 1 | objects: 2 | - &flag Apple 3 | - Beachball 4 | - Cartoon 5 | - Duckface 6 | - *flag 7 | 8 | lefthand: &A 9 | name: The Bastard Sword of Eowyn 10 | weight: 30 11 | 12 | righthand: *A 13 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/array.yml: -------------------------------------------------------------------------------- 1 | a: 2 | - 1 3 | - 2 4 | - 3 5 | - 4 6 | - 3 7 | - 2 8 | - 1 9 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/bad_indent.yml: -------------------------------------------------------------------------------- 1 | ort: 2 | allowedProcessEnvironmentVariableNames: 3 | - PASSPORT 4 | - USER_HOME 5 | deniedProcessEnvironmentVariablesSubstrings: 6 | - PASS 7 | - SECRET 8 | - TOKEN 9 | - USER 10 | enableRepositoryPackageCurations: true 11 | enableRepositoryPackageConfigurations: true 12 | 13 | licenseFilePatterns: 14 | licenseFilenames: [ 'license*' ] 15 | patentFilenames: [ patents ] 16 | rootLicenseFilenames: [ 'readme*' ] 17 | 18 | severeIssueThreshold: ERROR 19 | severeRuleViolationThreshold: ERROR 20 | 21 | analyzer: 22 | allowDynamicVersions: true 23 | enabledPackageManagers: [ Gradle, Yarn ] 24 | packageManagers: 25 | Yarn: 26 | options: 27 | directDependenciesOnly: true 28 | Gradle: 29 | options: 30 | directDependenciesOnly: true 31 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/base64.yml: -------------------------------------------------------------------------------- 1 | b: aGVsbG8gd29ybGQ= 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/basic.yml: -------------------------------------------------------------------------------- 1 | "a": "hello" 2 | "b": 1 3 | "c": true 4 | "d": 2.3 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/collections_error.yaml: -------------------------------------------------------------------------------- 1 | suppliers: 2 | - name: "A" 3 | code: aa 4 | parsers: 5 | - regex: ".*A.*json" 6 | kclass: supppliers.A 7 | 8 | - name: "B" 9 | code: bb 10 | parsers: 11 | - regex: ".*B.*" 12 | kclass: supppliers.B 13 | 14 | - name: "C" 15 | code: CC 16 | parsers: 17 | - regex: ".*C.*" 18 | kclass: supppliers.C 19 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/empty.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sksamuel/hoplite/e211e07c369b40a7dc9a706f1f0d144a9c457266/hoplite-yaml/src/test/resources/empty.yml -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/empty_with_comments.yml: -------------------------------------------------------------------------------- 1 | #a: 2 | # b: value 3 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/enums.yml: -------------------------------------------------------------------------------- 1 | a: Malbec 2 | b: MERLOT 3 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/fallback_1.yml: -------------------------------------------------------------------------------- 1 | a: foo 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/fallback_2.yml: -------------------------------------------------------------------------------- 1 | a: never 2 | b: voo 3 | c: woo -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/fallback_3.yml: -------------------------------------------------------------------------------- 1 | c: never 2 | d: roo -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/foo.yml: -------------------------------------------------------------------------------- 1 | foo: "bar" 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/github72.yml: -------------------------------------------------------------------------------- 1 | env: production 2 | 3 | redis: 4 | url: "redis://localhost:6379/" 5 | requestChannel: "foo" 6 | responseChannel: "foo" 7 | 8 | odb: 9 | home: "db" 10 | configFile: "config/production.conf" 11 | dbFile: "engine.db" 12 | server: false 13 | # dbFile ='abc 14 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/linked_hash_map.yml: -------------------------------------------------------------------------------- 1 | a: 2 | x: y 3 | z: c 4 | e: f 5 | g: h 6 | u: i 7 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/lonely.yml: -------------------------------------------------------------------------------- 1 | lonely: "foo" 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/month_day.yml: -------------------------------------------------------------------------------- 1 | a: --12-30 2 | b: 04-15 3 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/nested_basic_arrays.yml: -------------------------------------------------------------------------------- 1 | "a": "hello" 2 | "b": 3 | - "x" 4 | - "y" 5 | - "z" -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/nested_container_arrays.yml: -------------------------------------------------------------------------------- 1 | "a": "hello" 2 | "b": 3 | - "c": "hello" 4 | "d": true 5 | - "e": 1.4 6 | "f": 4 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/nulls.yml: -------------------------------------------------------------------------------- 1 | a: null 2 | b: 3 | c: "hello" 4 | d: null -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/numbers_map.yml: -------------------------------------------------------------------------------- 1 | counting: 2 | one: 3 | number: 1 4 | two: 5 | number: 2 6 | three: 7 | number: 3 8 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/principal.yaml: -------------------------------------------------------------------------------- 1 | name: sammy -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/props_decoder.yml: -------------------------------------------------------------------------------- 1 | myprops: 2 | foo: wibble 3 | bar: wobble 4 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/sealed_class.yml: -------------------------------------------------------------------------------- 1 | database: 2 | host: localhost 3 | port: 9200 4 | index: foo 5 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/sealed_class_list.yml: -------------------------------------------------------------------------------- 1 | databases: 2 | - host: localhost 3 | port: 9200 4 | index: foo 5 | - host: localhost 6 | port: 9300 7 | index: bar 8 | - host: localhost 9 | port: 5234 10 | schema: public 11 | table: faz 12 | 13 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/sealed_class_with_list_of_objects.yaml: -------------------------------------------------------------------------------- 1 | poolingStrategy: 2 | combo: [Omni, OsVersion] 3 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/sealed_class_with_object.yaml: -------------------------------------------------------------------------------- 1 | poolingStrategy: OsVersion 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/sealed_class_with_object_invalid_value.yaml: -------------------------------------------------------------------------------- 1 | poolingStrategy: Random 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/set_objects.yml: -------------------------------------------------------------------------------- 1 | a: 1,2,3,3,3 2 | b: 3 | - 1 4 | - 2 5 | - 1 6 | - 2 7 | - 1 8 | - 2 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/sets.yml: -------------------------------------------------------------------------------- 1 | a: 1,2,3,3,3 2 | b: 3 | - 1 4 | - 2 5 | - 1 6 | - 2 7 | - 1 8 | - 2 9 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/snake_case.yml: -------------------------------------------------------------------------------- 1 | dripDrop: hello 2 | tipTap: true 3 | double_trouble: 1,2,3 4 | wibble_wobble: 124.55 5 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test1.yml: -------------------------------------------------------------------------------- 1 | a: Sammy 2 | b: 1 3 | c: 12312313123 4 | d: true 5 | e: 10.4 6 | f: 5646.54 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_array.yml: -------------------------------------------------------------------------------- 1 | strings: 2 | - 1 3 | - 2 4 | - "a" 5 | - "b" 6 | longs: 7 | - 1 8 | - 2 9 | - 3 10 | - 4 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_array_as_delimited_string.yml: -------------------------------------------------------------------------------- 1 | strings: 1,2,a,b 2 | longs: 1,2,3,4 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_bigdecimal.yml: -------------------------------------------------------------------------------- 1 | a: 10 2 | b: 20.3334 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_biginteger.yml: -------------------------------------------------------------------------------- 1 | a: 10000 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_data_class_in_map.yaml: -------------------------------------------------------------------------------- 1 | m: 2 | DC1: 3 | x-val: 10 4 | DC2: 5 | x-val: 20 6 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_date.yml: -------------------------------------------------------------------------------- 1 | date: 2016-05-12 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_datetime.yml: -------------------------------------------------------------------------------- 1 | date: 2016-05-12T12:55:31Z -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_duration.yml: -------------------------------------------------------------------------------- 1 | millis: 5124ms 2 | seconds: 12s 3 | days: 3 days 4 | hours: 1 hour 5 | nanos: 10nanoseconds -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_duration_iso_8601.yml: -------------------------------------------------------------------------------- 1 | pushTimeout: "PT30S" 2 | pullTimeout: "PT1H" 3 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_enum.yml: -------------------------------------------------------------------------------- 1 | wine: Malbec -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_env_replacement.yml: -------------------------------------------------------------------------------- 1 | a: foo 2 | b: ${wibble} 3 | c: aa${wibble} 4 | d: ${wibble}bb 5 | e: aa${wibble}bb 6 | f: $wibble 7 | g: ${unknown:-default} 8 | h: ${wibble:-default} 9 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_env_replacement2.yml: -------------------------------------------------------------------------------- 1 | a: ${AA:-varvar} 2 | b: 3 | c: ${CC:-optionoption} 4 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_env_replacement3.yml: -------------------------------------------------------------------------------- 1 | foo: ${{ env:FOO }} 2 | bar: ${{ env:BAR }} 3 | baz: foo${{ env:BAR }}baz 4 | blopp: foo${{ env:NOTHING :- ${{ env:NONE :- ${{ env:BAR }}}} }}blopp 5 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_file.yml: -------------------------------------------------------------------------------- 1 | file: /home/user/sam -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_instant.yml: -------------------------------------------------------------------------------- 1 | instant: "2015-03-14T09:26:53.590Z" 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_ipnet.yml: -------------------------------------------------------------------------------- 1 | a: 10.0.0.2 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_lookup.yml: -------------------------------------------------------------------------------- 1 | a: foo 2 | b: b${a} 3 | c: c${a}.hello 4 | d: d${e:-def}.boo 5 | e: E 6 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_lookup1.yml: -------------------------------------------------------------------------------- 1 | env: PROD 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_lookup2.yml: -------------------------------------------------------------------------------- 1 | hostname: wibble.{{env}} 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_lookup_gha_style.yml: -------------------------------------------------------------------------------- 1 | a: foo 2 | b: b${{a}} 3 | c: c${{ a }}.hello 4 | d: d${{ e:- def }}.boo 5 | e: E 6 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_lookup_react.yml: -------------------------------------------------------------------------------- 1 | a: foo 2 | b: b${a} 3 | c: c{{a}}.hello 4 | d: d{{e:-def}}.boo 5 | e: E 6 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_map.yml: -------------------------------------------------------------------------------- 1 | map1: 2 | a: 11 3 | b: 22 4 | c: 33 5 | 6 | map2: 7 | 11: true 8 | 22: false 9 | 33: true -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_nested2.yml: -------------------------------------------------------------------------------- 1 | a: foo 2 | b: 3 | a: goo 4 | b: moo 5 | c: voo -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_nested3.yml: -------------------------------------------------------------------------------- 1 | x: foo 2 | y: true 3 | z: 4 | a: 5 | k: 12 6 | j: 2016-05-12 7 | b: 7924.3 8 | c: 91894781923 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_null_inline.yml: -------------------------------------------------------------------------------- 1 | user1: "sammy" 2 | user2: null 3 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_nulls.yml: -------------------------------------------------------------------------------- 1 | a: null 2 | b: null -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_path.yml: -------------------------------------------------------------------------------- 1 | path: /home/user/sam -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_random_preprocessor.yml: -------------------------------------------------------------------------------- 1 | c: ${random.int} 2 | d: ${random.boolean} 3 | e: ${random.int(9)} 4 | f: ${random.int(100,999)} 5 | g: ${random.long} 6 | h: ${random.double} 7 | i: hello ${random.string(14)} 8 | uuid: ${random.uuid} 9 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_set.yml: -------------------------------------------------------------------------------- 1 | strings: 2 | - 1 3 | - 2 4 | - "a" 5 | - "a" 6 | - "b" 7 | longs: 8 | - 1 9 | - 2 10 | - 3 11 | - 4 12 | - 4 13 | - 4 14 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_set_enum.yml: -------------------------------------------------------------------------------- 1 | wines: Malbec, Shiraz, Shiraz 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_sysproperty_replacement.yml: -------------------------------------------------------------------------------- 1 | a: foo 2 | b: ${wibble} 3 | c: aa${wibble} 4 | d: ${wibble}bb 5 | e: aa${wibble}bb 6 | f: $wibble 7 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_undefined.yml: -------------------------------------------------------------------------------- 1 | n: "hello" 2 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_urls.yml: -------------------------------------------------------------------------------- 1 | a: "http://www.google.com?search=noprivacy" 2 | b: http://www.google.com?search=noprivacy 3 | -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/test_uuid.yml: -------------------------------------------------------------------------------- 1 | uuid: 66cefa93-9816-4c09-aad9-6355664e3e4f -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/truthy_10.yml: -------------------------------------------------------------------------------- 1 | a: 1 2 | b: 0 -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/truthy_TF.yml: -------------------------------------------------------------------------------- 1 | a: T 2 | b: t 3 | c: f 4 | d: F -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/truthy_yesno.yml: -------------------------------------------------------------------------------- 1 | a: yes 2 | b: NO 3 | c: YES 4 | d: no 5 | e: true 6 | f: false -------------------------------------------------------------------------------- /hoplite-yaml/src/test/resources/value_type.yml: -------------------------------------------------------------------------------- 1 | server: 2 | host: localhost 3 | port: 1234 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sksamuel/hoplite/e211e07c369b40a7dc9a706f1f0d144a9c457266/logo.png --------------------------------------------------------------------------------