### Resource Resolver Usage Example Source: https://github.com/juxt/aero/blob/master/_autodocs/06-resolvers.md This example demonstrates how to use the resource-resolver with `read-config` to ensure configuration is loaded from classpath resources. ```clojure (read-config (io/resource "config.edn") {:resolver resource-resolver}) ``` -------------------------------- ### Comprehensive Real-World Configuration Example Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md A detailed example demonstrating a full application configuration using various features like environment variables, profile-specific settings, type conversions, references, and custom tags. This illustrates how to structure complex configurations for different environments. ```clojure ; resources/config.edn {:application {:name #uppercase #or [#env APP_NAME "MyService"] :version "1.0.0" :environment #profile {:default :dev :prod :production}} :server {:host #or [#env SERVER_HOST "0.0.0.0"] :port #long #or [#env SERVER_PORT 8080] :ssl #profile {:default false :prod true} :threads #long #or [#env SERVER_THREADS 16] :timeout-ms #long #or [#env REQUEST_TIMEOUT 30000]} :database {:primary {:url #or [#env PRIMARY_DB_URL "jdbc:h2:mem:test"] :pool-size #long #or [#env DB_POOL_SIZE 10] :connection-timeout #long 5000} :replica {:url #or [#env REPLICA_DB_URL #ref [:database :primary :url]] :pool-size #long #or [#env REPLICA_POOL_SIZE 5]}} :cache {:enabled #boolean #or [#env CACHE_ENABLED "true"] :ttl #long #or [#env CACHE_TTL 3600] :provider #profile {:default :memory :prod :redis}} :features #profile {:dev {:debug true :analytics false :experimental-ui true} :test {:debug false :analytics false :experimental-ui false} :prod {:debug false :analytics true :experimental-ui false}} :logging {:level #keyword #or [#env LOG_LEVEL "info"] :format #profile {:default :text :prod :json} :file #or [#env LOG_FILE nil]} :secrets #include #join [#env HOME "/.app/secrets.edn"]} ; Usage: ; For development: (read-config (io/resource "config.edn") {:profile :dev}) ; For production: (read-config (io/resource "config.edn") {:profile :prod}) ``` -------------------------------- ### Root Resolver Usage Example Source: https://github.com/juxt/aero/blob/master/_autodocs/06-resolvers.md This example shows how `read-config` with `root-resolver` treats include paths as absolute. For instance, `#include "/etc/app/database.edn"` will attempt to open the file at that absolute path. ```clojure (read-config "config.edn" {:resolver root-resolver}) ; Includes are treated as absolute paths: ; #include "/etc/app/database.edn" -> opens /etc/app/database.edn ``` -------------------------------- ### Example of Expansion Map Usage Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Demonstrates how to use the expand function and access values from the returned expansion map. Ensure aero.alpha.core is required. ```clojure (require '[aero.alpha.core :refer [expand]]) (let [result (expand {:greeting "Hello"} {:profile :default} {} [])] (:aero.core/value result) ; => {:greeting "Hello"} (:aero.core/incomplete? result) ; => false (:aero.core/env result)) ; => {[] {:greeting "Hello"} ...} ``` -------------------------------- ### Self-Documenting Configuration File Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md This example demonstrates a configuration file that includes comments explaining the purpose and format of settings, along with inline documentation for specific keys. It uses profile and environment variables for dynamic values. ```clojure ; resources/config.edn ;; Application Configuration ;; ;; Database Configuration ;; ===================== ;; URL format: protocol://host:port/database ;; Common values: ;; Development: datomic:mem://myapp ;; Production: datomic:sql://mysql://prod-db/myapp {:database {:url #profile {:dev "datomic:mem://myapp" :prod #env DATABASE_URL} ;; Connection pool size (number of concurrent connections) ;; Default: 10, Range: 5-100 :pool-size #long #or [#env DB_POOL_SIZE 10]}} ;; Server Configuration ;; =================== {:server {:port #long #or [#env SERVER_PORT 8080] :ssl #boolean #or [#env SERVER_SSL "false"] :timeout-ms #long 30000}}} ``` -------------------------------- ### Clojure Configuration Resolution Example Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Illustrates a sample EDN configuration file and its resolved structure after processing with `read-config`, showing how tags like #long, #or, #env, #include, and #profile are evaluated. ```clojure ; config.edn {:greeting "Hello World!" :port #long #or [#env PORT 8080] :database #include "db.edn" :features #profile {:dev {:debug true} :prod {:debug false}}} ; After read-config with {:profile :prod} {:greeting "Hello World!" :port 8080 :database {:url "postgres://..." :pool-size 10} :features {:debug false}} ``` -------------------------------- ### Deferred Usage Example Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Demonstrates creating a Deferred object and dereferencing its value. ```clojure (let [def-obj (deferred (+ 2 2))] (instance? Deferred def-obj) ; => true @(:delegate def-obj)) ; => 4 ``` -------------------------------- ### Validate Configuration on Startup Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Ensures that the application's configuration adheres to a defined schema before starting. Requires `clojure.spec.alpha` for validation. ```clojure (require '[clojure.spec.alpha :as s]) (defn validate-config! [config] (let [schema (s/keys :req-un [::database ::server])] (when-not (s/valid? schema config) (throw (ex-info "Invalid configuration" {:spec-error (s/explain-str schema config)}))))) (defn -main [& args] (try (let [config (aero/read-config (io/resource "config.edn") {:profile :prod})] (validate-config! config) (start-application config)) (catch Exception e (println "Configuration error:" (ex-message e)) (System/exit 1)))) ``` -------------------------------- ### Custom Conditional Tag Usage Example Source: https://github.com/juxt/aero/blob/master/_autodocs/05-alpha-api.md Demonstrates how to use the custom '#when' tag in a configuration file and how to provide the condition at runtime using `read-config`. ```clojure ; Use in config: ; {:debug-mode #when {:true {:level "debug"} :false {:level "info"}}} ; In code: (read-config "config.edn" {:condition :true}) ``` -------------------------------- ### Relative Path Resolution Example Source: https://github.com/juxt/aero/blob/master/_autodocs/06-resolvers.md Demonstrates how Aero's relative-resolver constructs paths. If the source is 'config/dev/config.edn' and the include is 'database.edn', it looks for 'config/dev/database.edn'. ```clojure ; If source is "config/dev/config.edn" and include is "database.edn" ; relative-resolver (used by adaptive-resolver) will look for: ; config/dev/database.edn ``` -------------------------------- ### User-Based Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Configure settings based on the current user. Use :default for users not explicitly listed. The example shows database URL configuration. ```clojure ; resources/config.edn {:database {:url #user {"dev-user" "datomic:mem://dev" "test-user" "datomic:mem://test" "prod-user" "datomic:sql://mysql://prod-db/myapp" :default "datomic:mem://default"}}} ``` -------------------------------- ### Hostname-Based Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Configure settings based on the current hostname. Use :default for hosts not explicitly listed. The example shows port and priority settings. ```clojure ; resources/config.edn {:server {:port #hostname {"web-server-1" 8080 "web-server-2" 8080 #{"backup-1" "backup-2"} 9080 :default 8000} :priority #hostname {"web-server-1" 100 "web-server-2" 100 #{"backup-1" "backup-2"} 1 :default 50}}} ``` -------------------------------- ### Define Configuration Data Source: https://github.com/juxt/aero/blob/master/README.md Example EDN file content for defining configuration. This data is read by the Aero library. ```clojure {:greeting "World!"} ``` -------------------------------- ### Accessing Values from Environment Map Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Provides an example of how to retrieve values from the Environment Map using key-paths. This demonstrates accessing the root and nested values. ```clojure (let [env {[] {:greeting "Hello" :port 8080} [:greeting] "Hello" [:port] 8080}] (get env []) ; => {:greeting "Hello" :port 8080} (get env [:greeting]) ; => "Hello" ) ``` -------------------------------- ### Custom Reader Implementation Accessing Options Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Shows an example of a custom reader implementation that can access custom keys like `:deployment-id` from the options map. ```clojure (defmethod reader 'deployment-aware [{:keys [deployment-id profile]} tag value] ; Can access custom :deployment-id ) ``` -------------------------------- ### Deferred Error Handling Example Source: https://github.com/juxt/aero/blob/master/_autodocs/04-deferred-macro.md Demonstrates how to define a reader that throws an error within a deferred computation. Errors are only realized upon dereferencing the deferred value. This example shows how to catch such exceptions. ```clojure (defmethod reader 'failing [opts tag value] (deferred (throw (ex-info "This fails later" {:when :dereferencing})))) ; In code: (let [config (read-config "config.edn")]; No error yet (try @(:my-deferred config) ; Error thrown here (catch Exception e (println "Error:" (ex-message e))))) ``` -------------------------------- ### TaggedLiteral Usage Example Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Shows how to access the tag and form components of a TaggedLiteral. ```clojure (let [tl (tagged-literal 'env "DATABASE_URL")] (:tag tl) ; => 'env (:form tl)) ; => "DATABASE_URL" ``` -------------------------------- ### Deferred Computation Example Source: https://github.com/juxt/aero/blob/master/_autodocs/00-README.md Utilize deferred computation for lazy evaluation of potentially expensive operations within the configuration. ```clojure (deferred (expensive-operation)) ``` -------------------------------- ### Key Path Vector Examples Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Illustrates the structure and usage of key path vectors for navigating nested data in Aero configurations. Vectors represent paths from the root to specific values or array elements. ```clojure [] ; Root [:database] ; First level [:database :url] ; Nested [:server :port] ; Nested [0] ; Array/vector index [:items 0 :name] ; Array element in object ``` -------------------------------- ### Basic Configuration with Defaults Source: https://github.com/juxt/aero/blob/master/_autodocs/INDEX.md Configure port, database URL, and debug mode with environment variables and default values. ```clojure {:port #long #or [#env PORT 8080] :database #or [#env DATABASE_URL "sqlite:///dev.db"] :debug #boolean #or [#env DEBUG "false"]} ``` -------------------------------- ### JVM Best Practice for Reading Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Demonstrates the recommended way to read configuration files from the classpath using `io/resource` for JVM applications. Avoids using a plain string path for configuration files in production. ```clojure ; Always use io/resource for classpath files (read-config (io/resource "config.edn")) ; Not recommended in production: (read-config "config.edn") ; May fail in JAR ``` -------------------------------- ### Basic File Includes Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Demonstrates how to include configuration from multiple files into a single configuration map using the #include directive. This promotes modularity in configuration. ```clojure ; resources/config.edn {:database #include "database.edn" :server #include "server.edn" :logging #include "logging.edn"} ; resources/database.edn {:primary {:url #env DATABASE_URL :pool-size 10} :replica {:url #env REPLICA_URL :pool-size 5}} ; resources/server.edn {:port #long #or [#env SERVER_PORT 8080] :threads #long #or [#env SERVER_THREADS 16]} ; resources/logging.edn {:level #keyword #or [#env LOG_LEVEL "info"] :format #or [#env LOG_FORMAT "text"]} ; Result (merged): ; {:database ; {:primary {:url "...", :pool-size 10} ; :replica {:url "...", :pool-size 5}} ; :server {:port 8080, :threads 16} ; :logging {:level :info, :format "text"}} ``` -------------------------------- ### Simple References in Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Demonstrates how to use #ref to reference other values within the same configuration file. This is useful for avoiding duplication of common settings. ```clojure ; resources/config.edn {:db-host "prod-db.example.com" :db-port 5432 :db-name "myapp" :primary-database {:host #ref [:db-host] :port #ref [:db-port] :name #ref [:db-name]} :replica-database {:host #ref [:db-host] :port #ref [:db-port] :name #ref [:db-name] :read-only true} :migration-database {:host #ref [:db-host] :port #ref [:db-port] :name #ref [:db-name]}} ; Result: ; {:db-host "prod-db.example.com" ; :db-port 5432 ; :db-name "myapp" ; :primary-database ; {:host "prod-db.example.com" ; :port 5432 ; :name "myapp"} ; :replica-database ; {:host "prod-db.example.com" ; :port 5432 ; :name "myapp" ; :read-only true} ; :migration-database ; {:host "prod-db.example.com" ; :port 5432 ; :name "myapp"}} ``` -------------------------------- ### Basic Key-Value Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Demonstrates reading a simple key-value configuration from an EDN file and accessing a value in code. ```clojure ; resources/config.edn {:app-name "MyApplication" :version "1.0.0" :greeting "Hello World!"} ; In code: (let [config (read-config (io/resource "config.edn"))] (println (:greeting config))) ; Output: Hello World! ``` -------------------------------- ### Reading Configuration with Custom Options Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Demonstrates how to read a configuration file with custom options provided in the options map. These custom keys are accessible within custom reader implementations. ```clojure (read-config "config.edn" {:profile :prod :environment "production" :deployment-id "deploy-12345"}) ``` -------------------------------- ### Retrieving Values with get-in Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Shows how to use `get-in` with key path vectors to retrieve values from nested configuration maps. This is a common pattern for accessing specific settings. ```clojure (let [config {:database {:url "postgres://localhost/mydb" :pool-size 10} :server {:port 8080}}] (get-in config [:database :url]) ; => "postgres://localhost/mydb" (get-in config [:database :pool-size]) ; => 10 (get-in config [:server :port])) ; => 8080 ``` -------------------------------- ### Multi-File Configuration with Includes Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Organize configuration into multiple files using `#include` and `#join` directives for better modularity. This pattern is suitable for large configurations, allowing organization by concern and easier overrides. ```clojure ; resources/config.edn {:database #include "database.edn" :server #include "server.edn" :logging #include "logging.edn" :secrets #include #join [#env HOME "/.app-secrets.edn"]} ; resources/database.edn {:url #or [#env DATABASE_URL "datomic:mem://app"] :pool-size #long #or [#env DB_POOL_SIZE 10]} ; resources/server.edn {:port #long #or [#env SERVER_PORT 8080] :ssl #boolean #or [#env SERVER_SSL "false"]} ``` -------------------------------- ### Basic Environment Variable Usage Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Shows how to use environment variables for configuration values like database URLs and API keys, including boolean parsing. ```clojure ; resources/config.edn {:database-url #env DATABASE_URL :api-key #env API_KEY :debug #boolean #or [#env DEBUG "false"]} ; Set before running: ; export DATABASE_URL="postgres://localhost/myapp" ; export API_KEY="secret-key-123" ; export DEBUG="true" ; In code: (let [config (read-config (io/resource "config.edn"))] (println "DB:" (:database-url config)) (println "Key:" (:api-key config)) (println "Debug:" (:debug config))) ; Output: ; DB: postgres://localhost/myapp ; Key: secret-key-123 ; Debug: true ``` -------------------------------- ### Basic Configuration with Defaults Source: https://github.com/juxt/aero/blob/master/_autodocs/00-README.md Define configuration with default values that can be overridden by environment variables. Use this for simple applications where environment variables are sufficient for customization. ```clojure ; config.edn {:port #long #or [#env PORT 8080] :database #or [#env DATABASE_URL "sqlite:///dev.db"] :debug #boolean #or [#env DEBUG "false"]} ; In code (let [cfg (read-config (io/resource "config.edn"))] (start-server :port (:port cfg))) ``` -------------------------------- ### Absolute Path Resolution Behavior Source: https://github.com/juxt/aero/blob/master/_autodocs/06-resolvers.md When an include path starts with '/', Aero treats it as an absolute path and uses it directly, ignoring the source path. ```clojure ; If include is "/etc/app/secrets.edn" ; It's treated as an absolute path regardless of source ``` -------------------------------- ### Import Full Core API Source: https://github.com/juxt/aero/blob/master/_autodocs/10-module-reference.md Import all functions from the core API. ```clojure (require '[aero.core :refer [read-config deferred reader resource-resolver root-resolver]]) ``` -------------------------------- ### Environment-Based File Include Paths Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Illustrates using environment variables to dynamically construct the path for file includes. This allows for flexible configuration loading based on the execution environment. ```clojure ; resources/config.edn {:database #include #join [#env HOME "/.secrets/database.edn"] :api #include "api-config.edn"} ; In code with environment: ; export HOME=/home/user ; The config will include /home/user/.secrets/database.edn ``` -------------------------------- ### Environment-Aware Password Reader Source: https://github.com/juxt/aero/blob/master/_autodocs/03-reader-multimethod.md An example of a custom reader that reads a secret differently based on the environment profile. It reads from a vault in production and uses a default for other profiles. ```clojure (defmethod reader 'secret [{:keys [profile] :as opts} tag secret-name] (if (= profile :prod) (read-secret-from-vault secret-name) "default-test-password")) ; In config.edn: ; {:database-password #secret "db-password"} ``` -------------------------------- ### Custom Reader Tag Source: https://github.com/juxt/aero/blob/master/_autodocs/00-README.md Define and use custom reader tags for specific transformations. This example shows how to create an `#uppercase` tag to convert string values to uppercase. ```clojure ; Register custom reader (defmethod reader 'uppercase [_ _ value] (clojure.string/upper-case (str value))) ; Use in config {:name #uppercase "my app"} ; Result: {:name "MY APP"} ``` -------------------------------- ### Single File Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Use a single EDN file for all application configuration. This approach is recommended for its simplicity, ease of diffing, and reduced deployment complexity. ```clojure ; resources/config.edn - single source of truth {:database {:url "..." :pool-size 10} :server {:port 8080 :ssl false} :logging {:level :info} :auth {:provider :oauth} :feature-flags {:new-ui true :beta-api false}} ``` -------------------------------- ### Error Handling with ExceptionInfo Source: https://github.com/juxt/aero/blob/master/_autodocs/03-reader-multimethod.md Implement structured error handling in a custom reader by throwing ExceptionInfo. This example shows parsing a value as a long and providing context on failure. ```clojure (defmethod reader 'strict-long [opts tag value] (try #?(:clj (Long/parseLong (str value))) #?(:cljs (js/parseInt (str value))) (catch Exception e (throw (ex-info (format "Cannot parse '%s' as long" value) {:tag tag :value value :original-error e}))))) ) ``` -------------------------------- ### Component Integration with Aero Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Manage Aero configuration as a component within a Stuart Sierra Component system. The configuration is loaded during the component's start lifecycle. ```clojure (require '[com.stuartsierra.component :as component] '[aero.core :as aero]) (defrecord ConfigComponent [profile] component/Lifecycle (start [this] (let [config (aero/read-config (io/resource "config.edn") {:profile profile})] (assoc this :config config))) (stop [this] this)) (defn config-component [profile] (->ConfigComponent profile)) ; In system map: (component/system-map :config (config-component :prod) :database (component/using (database-component) [:config]) :server (component/using (server-component) [:config :database])) ``` -------------------------------- ### Basic Usage: Read Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/INDEX.md Demonstrates how to read a configuration file (e.g., `config.edn`) for a specific profile (e.g., `:prod`) using `aero.core/read-config`. ```clojure (require '[aero.core :refer [read-config]] '[clojure.java.io :as io]) ; Read configuration for a profile (let [config (read-config (io/resource "config.edn") {:profile :prod})] (println "Server port:" (get-in config [:server :port]))) ``` -------------------------------- ### Using Tagged Literals in Custom Reader Source: https://github.com/juxt/aero/blob/master/_autodocs/03-reader-multimethod.md Compose custom readers to evaluate other tag literals or nested configurations. This example shows a 'compose' reader that concatenates two parts. ```clojure (defmethod reader 'compose [{:keys [profile]} tag {:keys [part1 part2]}] (str part1 "-" part2)) ; Use with other tags: ; {:key #compose {:part1 #env DOMAIN :part2 #env ENVIRONMENT}} ``` -------------------------------- ### Define Custom Tag Literal Source: https://github.com/juxt/aero/blob/master/_autodocs/02-tag-literals.md Extend the `reader` multimethod to define a custom tag literal. This example shows how to create a 'decrypt' tag for processing encrypted secrets. It requires the `aero.core` namespace. ```clojure (require '[aero.core :refer [reader]]) ; Define custom tag for decrypting secrets (defmethod reader 'decrypt [opts tag encrypted-value] (decrypt-secret encrypted-value)) ``` -------------------------------- ### Optimize Configuration Performance Source: https://github.com/juxt/aero/blob/master/_autodocs/09-error-handling.md Avoid forward references where possible by structuring your configuration logically. Defer expensive operations using the `deferred` tag to improve reading speed. ```clojure ; Avoid forward references where possible: ; Instead of: {:main {:value #ref [:later]} :later 42} ; Use: {:later 42 :main {:value #ref [:later]}} ; Defer expensive operations: (defmethod reader 'expensive [opts tag value] (deferred (expensive-operation value))) ; Deferred, fast ; Reduce circular references and forward refs ``` -------------------------------- ### Custom Partial Expansion Tag Implementation Source: https://github.com/juxt/aero/blob/master/_autodocs/05-alpha-api.md Implements a custom '#lazy-map' tag that allows for fine-grained control over expansion. This specific example expands only the keys of a map, leaving the values as tagged literals. ```clojure (defmethod eval-tagged-literal 'lazy-map [tl opts env ks] ; Expand keys but not values (values stay as tagged literals) (let [{:keys [:aero.core/value :aero.core/env]} (expand-keys (:form tl) opts env ks)] {:aero.core/value value :aero.core/env env})) ``` -------------------------------- ### Formatted Environment Variables Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Illustrates using formatted environment variables to construct complex configuration strings, such as database connection URLs and API endpoints. ```clojure ; resources/config.edn {:database #envf ["postgresql://%s:%s@%s/myapp" DB_USER DB_PASSWORD DB_HOST] :api-url #envf ["https://%s/v%s/api" API_DOMAIN API_VERSION]} ; Set environment: ; export DB_USER=appuser ; export DB_PASSWORD=mypassword ; export DB_HOST=db.example.com ; export API_DOMAIN=api.example.com ; export API_VERSION=2 ; Result: ; {:database "postgresql://appuser:mypassword@db.example.com/myapp" ; :api-url "https://api.example.com/v2/api"} ``` -------------------------------- ### Deferred Secret Decryption Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Use deferred evaluation to delay expensive operations like secret decryption until runtime, especially in production environments. This example shows how to decrypt secrets only when the profile is set to :prod. ```clojure ; In reader implementation: (defmethod reader 'decrypt-secret [{:keys [profile]} tag encrypted-value] (if (= profile :prod) (deferred ; Only decrypt in prod (decrypt-with-kms encrypted-value)) "development-default")) ; resources/config.edn {:api-key #decrypt-secret "KMS:base64encodedencrypted" :db-password #decrypt-secret "KMS:anothersecret"} ; In code: ; When using {:profile :dev}: ; - deferred operations are not called ; - result: {:api-key "development-default", :db-password "development-default"} ; - FAST - no KMS calls ; When using {:profile :prod}: ; - deferred operations are evaluated during config reading ; - result: {:api-key "decrypted-key", :db-password "decrypted-password"} ; - Takes time for KMS calls but ensures secrets are available ``` -------------------------------- ### Manage Environment-Specific Settings with #profile Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Use #profile to define different configuration values for various environments like 'dev', 'test', and 'prod', with a 'default' fallback. This is useful for managing distinct settings across development, testing, and production deployments. ```clojure ; resources/config.edn {:server {:port #profile {:default 8000 :dev 8001 :test 8002 :prod 80} :ssl #profile {:default false :dev false :test false :prod true} :enable-cors #profile {:default false :dev true :test true :prod false}} :database {:url #profile {:default "datomic:mem://app" :dev "datomic:mem://dev" :test "datomic:mem://test" :prod #env DATABASE_URL}} :logging {:level #profile {:default :info :dev :debug :test :warn :prod :info} :format #profile {:default :simple :dev :detailed :test :minimal :prod :json}}} ; Usage: (read-config (io/resource "config.edn") {:profile :dev}) ; Result uses :dev branch for #profile tags (read-config (io/resource "config.edn") {:profile :prod}) ; Result uses :prod branch for #profile tags ``` -------------------------------- ### Include Another Configuration File with #include Source: https://github.com/juxt/aero/blob/master/README.md Use #include to split large configuration files into smaller, manageable ones. By default, files are resolved relative to the including file. ```clojure {:webserver #include "webserver.edn" :analytics #include "analytics.edn"} ``` ```clojure (require '[aero.core :refer (read-config resource-resolver)]) (read-config "config.edn" {:resolver resource-resolver}) ``` ```clojure (read-config "config.edn" {:resolver {"webserver.edn" "resources/webserver/config.edn"}}) ``` -------------------------------- ### Read Configuration from Classpath Source: https://github.com/juxt/aero/blob/master/README.md Read configuration from the classpath using `io/resource`. This is the recommended approach for production applications to ensure configuration is available when packaged into JAR files. ```clojure (read-config (clojure.java.io/resource "config.edn")) ``` -------------------------------- ### Profile-Based Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Use this pattern for a single configuration file that defines different settings for various profiles like development, testing, and production. It allows for easy visibility of all variations. ```clojure ; Single file with profiles {:database #profile {:dev "datomic:mem://dev" :test "datomic:mem://test" :prod "datomic:sql://mysql://prod-db/myapp"} :server #profile {:dev {:port 3000 :auto-reload true} :test {:port 0} ; Use random port in tests :prod {:port 80 :auto-reload false}}} ``` -------------------------------- ### Reference User with #user Source: https://github.com/juxt/aero/blob/master/README.md Use #user similarly to #hostname, but it switches configuration values based on the current user. ```clojure {:setting #user {"alice" "value1" "bob" "value2" :default "default_value"}} ``` -------------------------------- ### Read Configuration with Options Source: https://github.com/juxt/aero/blob/master/_autodocs/10-module-reference.md Reads an EDN configuration file and resolves all tag literals. Use `read-config` for basic reading and `read-config` with `given-opts` to provide custom options. ```clojure (read-config source) (read-config source given-opts) ``` -------------------------------- ### Construct URLs with #join and #or Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Construct URLs by joining literal strings and environment variables. The #or macro provides a default value if an environment variable is not set, useful for base URLs. ```clojure ; resources/config.edn {:api {:base-url #join ["https://" #or [#env API_DOMAIN "localhost:3000"]] :v1-endpoint #join [#ref [:api :base-url] "/v1"] :v2-endpoint #join [#ref [:api :base-url] "/v2"]}} ; Result: ; {:api ; {:base-url "https://api.example.com" ; :v1-endpoint "https://api.example.com/v1" ; :v2-endpoint "https://api.example.com/v2"}} ``` -------------------------------- ### Import Minimal Core API Source: https://github.com/juxt/aero/blob/master/_autodocs/10-module-reference.md Import the minimal required functions from the core API. ```clojure (require '[aero.core :refer [read-config]]) ``` -------------------------------- ### Profile-Based Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/INDEX.md Set server port and SSL based on the active profile (e.g., dev or prod). ```clojure {:server {:port #profile {:dev 8001 :prod 80} :ssl #profile {:dev false :prod true}}} ``` -------------------------------- ### Directory-Based Configuration Loading Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md This pattern organizes configuration into separate files for different profiles (e.g., base, dev, test, prod) within a directory structure. It uses a function to determine and read the appropriate configuration file based on the active profile. ```clojure ; resources/config/ ; ├── base.edn (shared configuration) ; ├── dev.edn (dev overrides) ; ├── test.edn (test overrides) ; └── prod.edn (prod overrides) (defn config-file [profile] (case profile :dev "config/dev.edn" :test "config/test.edn" :prod "config/prod.edn")) (defn load-config [profile] (aero/read-config (io/resource (config-file profile)))) ``` -------------------------------- ### Provide Default Value with #or Source: https://github.com/juxt/aero/blob/master/README.md Use #or to provide a list of possibilities, with a default value at the end. This is useful for optional configuration values. ```clojure {:port #or [#env PORT 8080]} ``` -------------------------------- ### Global Configuration Singleton with Aero Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Employ this pattern for applications that load configuration once at startup. It caches the configuration globally, avoiding parameter threading and establishing a clear initialization order. ```clojure (ns myapp.config (:require [aero.core :as aero] [clojure.java.io :as io])) (def ^:private _config (atom nil)) (defn load-config! [profile] "Load configuration and cache it globally" (reset! _config (aero/read-config (io/resource "config.edn") {:profile profile}))) (defn get-config [] "Get cached configuration" (if @_config @_config (throw (ex-info "Configuration not loaded. Call load-config! first." {})))) ; In -main: (defn -main [& args] (load-config! :prod) (start-application)) ``` -------------------------------- ### Simple Configuration Module with Aero Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Use this pattern to create a dedicated namespace for configuration management. Wrapper functions help isolate code from config structure changes and facilitate testing. ```clojure ; src/myapp/config.clj (ns myapp.config (:require [aero.core :as aero] [clojure.java.io :as io])) (defn config [profile] "Load configuration for the given profile" (aero/read-config (io/resource "config.edn") {:profile profile})) (defn db-url [config] "Extract database URL from configuration" (get-in config [:database :url])) (defn server-port [config] "Extract server port from configuration" (get-in config [:server :port])) ; In application code: (require '[myapp.config :as config]) (let [cfg (config/config :prod)] (start-server :port (config/server-port cfg))) ``` -------------------------------- ### Align System Map and Configuration Map with merge-with Source: https://github.com/juxt/aero/blob/master/README.md A pattern for aligning system components with configuration data using `merge-with merge`. This approach simplifies passing configuration to component constructors. ```clojure {:listener {:port 8080} :database {:uri "datomic:mem://myapp/dev"}} ``` ```clojure (defrecord Listener [database port] Lifecycle …) (defn new-listener [] (using (map->Listener {}) [:database]) (defrecord Database [uri] Lifecycle …) (defn new-database [] (map->Database {})) (defn new-system-map "Create a configuration-free system" [] (system-map :listener (new-listener) :database (new-database))) (defn configure [system profile] (let [config (aero/read-config "config.edn" {:profile profile})] (merge-with merge system config))) (defn new-dependency-map [] {}) (defn new-system "Create the production system" [profile] (-> (new-system-map) (configure profile) (system-using (new-dependency-map)))) ``` -------------------------------- ### Reference Other Configuration Parts with #ref Source: https://github.com/juxt/aero/blob/master/README.md Use #ref to avoid duplication by referring to other parts of your configuration file using a vector path resolvable by `get-in`. References are recursive and can be used in included files. ```clojure {:db-connection "datomic:dynamo://dynamodb" :webserver {:db #ref [:db-connection]} :analytics {:db #ref [:db-connection]}} ``` -------------------------------- ### Multiple Fallback Options with #or Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Chain multiple #env lookups within #or to provide a sequence of fallback options, ending with a hardcoded default. This allows for explicit settings, aliases, and final defaults. ```clojure ; resources/config.edn {:log-level #or [#env LOG_LEVEL ; First try explicit setting #env VERBOSITY ; Fall back to alias "info"] ; Final default :feature-flags {:use-cache #or [#env USE_CACHE (not= :test #env APP_ENV) false]}} ``` -------------------------------- ### TaggedLiteral Constructor Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Demonstrates how to create a TaggedLiteral instance. ```clojure (tagged-literal 'mytag "my-value") ; Returns: # ``` -------------------------------- ### Import Core API with Macro Source: https://github.com/juxt/aero/blob/master/_autodocs/10-module-reference.md Import core API functions and macros, including specific handling for ClojureScript. ```clojure (require '[aero.core :refer [read-config deferred reader] :refer-macros [deferred]]) ; ClojureScript ``` -------------------------------- ### Read Configuration from File Source: https://github.com/juxt/aero/blob/master/README.md Read configuration from a specified EDN file using `read-config`. This method is suitable for REPL and test environments. ```clojure (require '[aero.core :refer [read-config]]) (read-config "config.edn") ``` -------------------------------- ### Load and Validate Configuration with Plumatic Schema Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Use this pattern to load configuration and validate its structure and types against a Plumatic Schema. Requires `schema.core` and `aero.core`. ```clojure (require '[schema.core :as s] '[aero.core :as aero]) (def ConfigSchema {:database {:url s/Str :pool-size s/Int} :server {:port s/Int :ssl s/Bool}}) (defn load-validated-config [profile] (let [config (aero/read-config (io/resource "config.edn") {:profile profile})] (s/validate ConfigSchema config))) ; Validates configuration structure and types (load-validated-config :prod) ``` -------------------------------- ### Deferred Creation Source: https://github.com/juxt/aero/blob/master/_autodocs/08-types-and-data-structures.md Illustrates how to create a Deferred object using the 'deferred' function. ```clojure (require '[aero.core :refer [deferred]]) (deferred (expensive-operation)) ``` -------------------------------- ### Profile with Default Fallback Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Configure a setting using #profile where a specific profile (e.g., ':prod') has a unique value, while all other profiles (including unspecified ones) fall back to the ':default' value. ```clojure ; resources/config.edn {:feature {:beta-ui #profile {:prod false :default true}}} ; Using :dev, :test, or other profiles will use :default branch ; Only :prod profile gets false, others get true ``` -------------------------------- ### Read EDN Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/00-README.md Loads and resolves EDN configuration files. Specify the file path and optionally a profile map. ```clojure (read-config "config.edn" {:profile :prod}) ``` -------------------------------- ### Integrate Aero with Plumatic Schema Components Source: https://github.com/juxt/aero/blob/master/README.md Demonstrates how to integrate Aero configuration with the 'component' library for managing application lifecycle and dependencies. ```clojure (ns myproj.server (:require [myproj.config :as config])) (defrecord MyServer [config] Lifecycle (start [component] (assoc component :server (start-server :port (config/webserver-port config)))) (stop [component] (when-let [server (:server component)] (stop-server server)))) (defn new-server [config] (->MyServer config)) ``` ```clojure (ns myproj.system [com.stuartsierra.component :as component] [myproj.server :refer [new-server]]) (defn new-production-system [] (let [config (config/config :prod)] (system-using (component/system-map :server (new-server config)) {}))) ``` -------------------------------- ### Read Config with Profile Selection Source: https://github.com/juxt/aero/blob/master/_autodocs/01-read-config.md Reads a configuration file and selects a specific profile for resolution. This is useful for environment-specific settings. ```clojure (read-config "config.edn" {:profile :prod}) ; Uses :prod branch of #profile tags in config.edn ``` -------------------------------- ### Check JVM Classpath for Resources (Java/Clojure) Source: https://github.com/juxt/aero/blob/master/_autodocs/09-error-handling.md Ensure resources are accessible on the JVM classpath by checking `io/resource`. Use `java -cp myapp.jar:. clojure.main -e ...` to verify the classpath. ```clojure ; io/resource returns nil if resource not found (io/resource "config.edn") ; Must be in classpath ; Check classpath: java -cp myapp.jar:. clojure.main -e "(println (io/resource \"config.edn\"))" ``` -------------------------------- ### Import Alpha API Source: https://github.com/juxt/aero/blob/master/_autodocs/10-module-reference.md Import advanced functions from the alpha API for custom configuration expansion. ```clojure (require '[aero.alpha.core :refer [expand expand-coll expand-scalar expand-scalar-repeatedly expand-case eval-tagged-literal kv-seq reassemble]]) ``` -------------------------------- ### Read Configuration with Command-Line Profile Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Reads configuration from 'config.edn' using a profile specified via command-line arguments. Defaults to 'dev' if no profile is provided. ```clojure (require '[clojure.tools.cli :refer [parse-opts]]) (defn -main [& args] (let [opts (parse-opts args [["-p" "--profile PROFILE"] "Set configuration profile" :default "dev"])) profile (keyword (:profile (:options opts))) config (aero/read-config (io/resource "config.edn") {:profile profile})] (start-application config))) ``` -------------------------------- ### Include Configuration File Source: https://github.com/juxt/aero/blob/master/_autodocs/02-tag-literals.md Include another configuration file and use its contents. The included file is resolved relative to the source file by default. ```clojure #include "path/to/file.edn" ``` ```clojure {:database #include "database.edn" :webserver #include "webserver.edn" :secrets #include #join [#env HOME "/.secrets.edn"]} ``` -------------------------------- ### #include Source: https://github.com/juxt/aero/blob/master/_autodocs/02-tag-literals.md Includes the contents of another configuration file into the current one. The path is resolved relative to the source file by default, but can be controlled by the :resolver option. ```APIDOC ## #include ### Description Includes another configuration file and uses its contents. The included file is resolved relative to the source file by default, but full file resolution is controlled by the `:resolver` option. ### Signature ```clojure #include "path/to/file.edn" ``` ### Parameters #### Path Parameters - **Path** (String) - Relative or absolute path to include file. Resolution depends on configured resolver. ### Returns Parsed configuration from the included file (typically a map). ### Example ```clojure {:database #include "database.edn" :webserver #include "webserver.edn" :secrets #include #join [#env HOME "/.secrets.edn"]} ``` With `database.edn` containing: ```clojure {:host "localhost" :port 5432 :name "myapp"} ``` The result merges the included content: ```clojure {:database {:host "localhost" :port 5432 :name "myapp"} :webserver {...} :secrets {...}} ``` ### Throws `ExceptionInfo` if include file not found (resolver returns `{:aero/missing-include "filename"}`). ``` -------------------------------- ### Define Configuration Namespace and Accessor Functions Source: https://github.com/juxt/aero/blob/master/README.md Create a dedicated namespace for configuration and provide functions to access it. This pattern insulates your program from changes in the configuration file structure. ```clojure (ns myproj.config (:require [aero.core :as aero])) (defn config [profile] (aero/read-config "dev/config.edn" {:profile profile})) (defn webserver-port [config] (get-in config [:webserver :port])) ``` -------------------------------- ### Profile-Based Configuration Source: https://github.com/juxt/aero/blob/master/_autodocs/00-README.md Configure settings that vary based on the application's profile (e.g., development, production). This is useful for managing environment-specific parameters like ports or SSL settings. ```clojure ; config.edn {:server {:port #profile {:dev 8001 :prod 80} :ssl #profile {:dev false :prod true}}} ; For development (read-config (io/resource "config.edn") {:profile :dev}) ; For production (read-config (io/resource "config.edn") {:profile :prod}) ``` -------------------------------- ### Join Database Connection Strings Source: https://github.com/juxt/aero/blob/master/_autodocs/11-configuration-examples.md Use the #join reader macro to construct database connection strings by concatenating literal strings and environment variables. Ensure all necessary environment variables are set for a valid connection string. ```clojure ; resources/config.edn {:database {:url #join ["jdbc:postgresql://" #env DB_HOST ":" #env DB_PORT "/myapp" "?user=" #env DB_USER "&password=" #env DB_PASSWORD "&sslmode=require"]}} ; With environment: ; export DB_HOST=db.example.com ; export DB_PORT=5432 ; export DB_USER=appuser ; export DB_PASSWORD=secure123 ; Result: ; {:database ; {:url "jdbc:postgresql://db.example.com:5432/myapp?user=appuser&password=secure123&sslmode=require"}} ``` -------------------------------- ### Check JVM System Properties (Java/Clojure) Source: https://github.com/juxt/aero/blob/master/_autodocs/09-error-handling.md Verify JVM system properties by checking the output of `System/getProperties`. Use `java -Dmy.property=value -jar app.jar` to set properties before running. ```clojure ; #prop returns nil if property not set ; Check with System/getProperties (System/getProperties) ; Set system property before running: java -Dmy.property=value -jar app.jar ``` -------------------------------- ### Resolve Include File Path Source: https://github.com/juxt/aero/blob/master/_autodocs/09-error-handling.md Verify the file path for includes. Use an absolute path or ensure the resolver is correctly configured, especially for classpath resources. ```clojure ; Check file path (io/exists? (io/file "path/to/file.edn")) ; Use absolute path if relative fails (read-config "config.edn" {:resolver (fn [_ include] (io/file "/etc/app" include))}) ; Verify resolver is set correctly (read-config "config.edn" {:resolver resource-resolver}) ; For classpath resources ``` -------------------------------- ### Read Configuration with Environment Profile Source: https://github.com/juxt/aero/blob/master/_autodocs/07-integration-patterns.md Reads configuration from 'config.edn' using a profile determined by the APP_ENV environment variable. Defaults to 'dev' if the variable is not set. ```clojure (defn current-profile [] (let [env (System/getenv "APP_ENV")] (keyword (or env "dev")))) (defn -main [& args] (let [config (aero/read-config (io/resource "config.edn") {:profile (current-profile)})] (start-application config))) ``` -------------------------------- ### Accessing Options in Custom Reader Source: https://github.com/juxt/aero/blob/master/_autodocs/03-reader-multimethod.md Implement a custom reader by defining a multimethod that accepts options like profile, hostname, user, source, and resolver. ```clojure (defmethod reader 'profile-aware [{:keys [profile hostname user source resolver]} tag value] ; profile - the selected profile (keyword) ; hostname - system hostname (string) ; user - system user (string) ; source - path to the config file being read ; resolver - the include resolver function/map ) ``` -------------------------------- ### Type Checking Configuration Values Source: https://github.com/juxt/aero/blob/master/_autodocs/09-error-handling.md After reading configuration, use `assert` to verify that individual configuration values have the expected types. This helps catch type-related errors early in the application lifecycle. ```clojure ; After reading, verify types: (let [config (read-config "config.edn")] (assert (string? (:database-url config))) (assert (integer? (get-in config [:server :port]))) (assert (boolean? (get-in config [:debug])))) ```