NAME
    Config::Structured - provides generalized and structured configuration
    value access

SYNOPSIS
    Basic usage:

      use Config::Structured;

      my $conf = Config::Structured->new(
        structure => { 
          db => {
            dsn     => {
              isa         => 'Str',
              default     => '',
              description => 'Data Source Name for connecting to the database',
              url         => "https://en.wikipedia.org/wiki/Data_source_name",
              examples    => ["dbi:SQLite:dbname=:memory:", "dbi:mysql:host=localhost;port=3306;database=prod_myapp"]
            },
            username => {
              isa         => 'Str',
              default     => 'dbuser',
              description => "the database user's username",
            },
            password => {
              isa         => 'Str',
              description => "the database user's password",
              sensitive   => 1,
              notes       => "Often ref'd via file or ENV for security"
            },
          }
        },
        config => { 
          db => {
            username => 'appuser',
            host     => {
              source   => 'env',
              ref      => 'DB_HOSTNAME',
            },
            password => {
              source => 'file',
              ref    => '/run/secrets/db_password',
            },
          }
        }
      );

      say $conf->db->username(); # appuser
      # assuming that the hostname value has been set in the DB_HOSTNAME env var
      say $conf->db->host; # prod_db_1.mydomain.com
      # assuming that the password value has been stored in /run/secrets/db_password
      say $conf->db->password(1); # *mD9ua&ZSVzEeWkm93bmQzG

    Hooks example showing how to ensure config directories exist prior to
    first use:

      use File::Path qw(make_path);

      my $conf = Config::Structured->new(
        ...
        hooks => {
          '/paths/*' => {
            on_load => sub($node,$value) {
              make_path($value)
            }
          }
        }
      )

DESCRIPTION
    Config::Structured is a configuration value manager and accessor. Its
    design is based on the premise of predefining a structure (which is
    essentially a schema plus some metadata) to which the configuration must
    adhere. This has the effect of ensuring that when the application
    accesses its configuration, it has confidence that the values are of
    appopriate types, defaults are declared in a consistent manner, and new
    configuration nodes cannot be added ad hoc (i.e., without being declared
    within the structure).

    A configuration structure is a hierarchical system of nodes. Nodes may
    be branches (containing only other nodes) or leaves (identified by their
    "isa" key). Any keys are allowed within a leaf node, for custom tracking
    of arbitrary metadata, but the following are handled specially by
    "Config::Structured":

    "isa"
        Required

        Type constraint against which the configured value for the given key
        will be checked. See Moose::Util::TypeConstraints. Can be set to
        "Any" to opt out of type checking. If a typecheck fails, the
        on_typecheck_error handler is invoked.

    "default"
        Optional

        This key's value is the default configuration value if a data source
        or value is not provided by the configuation.

    "sensitive"
        Optional

        Set to true to mark this key's value as sensitive (e.g., password
        data). Sensitive values will be returned as a string of asterisks
        unless a truth-y value is passed to the accessor

            use builtin qw(true);

            conf->db->pass        # ************
            conf->db->pass(true)  # uAjH9PmjH9^knCy4$z3TM4

        This behavior is mimicked in "to_hash" and "get_node".

    "description"
        Optional

        A human-readable description of the configuration option.

    "notes"
        Optional

        Human-readable implementation notes of the configuration node.

    "examples"
        Optional

        One or more example values for the given configuration node.

    "url"
        Optional

        A web URL to additional information about the configuration node or
        resource

CONSTRUCTORS
  Config::Structured->new( %params )
    Returns a "Config::Structured" node (a dynamically-generated subclass of
    "Config::Structured::Node"). Nodes implement all methods in the METHODS
    section, plus those corresponding to the configuration keys defined in
    their structure definition.

    Parameters:

   structure
    Required

    Either a string or a HashRef. If a string is passed, it is handed off to
    Data::Structure::Deserialize::Auto, which attempts to parse a YAML,
    JSON, TOML, or perl string value or filename of an existing, readable
    file containing data in one of those formats, into its corresponding
    perl data structure. The format of such a structure is detailed in the
    "DESCRIPTION" section.

   config
    Required

    Either a string or a HashRef. If a string is passed, it is handed off to
    Data::Structure::Deserialize::Auto, which attempts to parse a YAML,
    JSON, TOML, or perl string value or filename of an existing, readable
    file containing data in one of those formats, into its corresponding
    perl data structure. Its format should mirror that of its "structure"
    except that its leaf nodes should contain the configured value for that
    key.

    In some cases, however, it is inconvenient or insecure to store the
    configuation value here (such as with passwords). In that case, the
    actual configuration value may be stored in a separate file or an
    environment variable, and a reference may be used in "config" to point
    to it. To invoke this behavior, the node's "isa" must be a string type
    (such as "Str" or "Str|Undef"). Then, set the config value to a HashRef
    containing two keys:

    *   source - "file" or "env"

    *   ref - the filesystem path (relative or absolute) or the name of the
        environment variable holding the value

    If the value is pulled from a file, it will be chomp
    <https://perldoc.perl.org/functions/chomp>ed.

   hooks
    Optional

    A HashRef whose keys are config paths. A config path is a
    slash-separated string of config node keys, beginning with a root slash.
    Asterisks are valid placeholders for full or partial path components.
    E.g.:

        /db/user
        /db/*
        /email/recipients/admin_*
        /*/password

    The values corresponding to these keys are HashRefs whose keys are
    supported hook types. Two types of hooks are supported:

    *   on_load - these hooks are run once, when the applicable config node
        is constructed

    *   on_access - these hooks are run each time the applicable config node
        is invoked

    The values corresponding to those keys are CodeRefs (or ArrayRefs of
    CodeRefs) to run when the appropriate events occur on the specified
    config paths.

    The hook function is passed two arguments: the configuration node path,
    and the configuration value (which is not obscured, even for sensitive
    data nodes)

   on_typecheck_error
    Optional.

    Controls the behavior occurring when a value type constraint check
    fails.

    *   fail - die with an error message about the constraint failure

    *   warn (default) - emit a warning and set the value to undef

    *   undef (or any other value) - do nothing and set the value to undef

METHODS
  to_hash( $reveal_sensitive = 0 )
    Returns the entire configuration tree as hashref. Sensitive values are
    obscured unless $reveal_sensitive is true.

  get_node( $child = undef, $reveal_sensitive = 0 )
    Get all data and metadata for a given node. If given, $child is the name
    of a direct child node to get the data for, otherwise data for the
    called object is returned. For leaf nodes, sensitive values are obscured
    unless $reveal_sensitive is true.

    Returns a HashRef which always contains the following keys:

    *   "path" - the full configuration path of the node

    *   "depth" - how many levels deep this node is in the config (1-based)

    *   "branches" - ArrayRef of the names of all branch children of this
        node

    *   "leaves" - ArrayRef of the names of all leaf children of this node

    Additionally, for leaf nodes:

    *   "value" - the value of the configuration node (possibly obscured)

    *   "overridden" - boolean value that reflects whether the configuration
        value for this node is the default (0) or from "config" (1)

    *   "reference" - present only if the node uses a "Referenced Value", in
        which case it is a HashRef containing the "source" and "ref" keys
        and values

    *   {structure keys} - all keys and values from the node's structure are
        present as well (e.g., "isa", "description", etc., as well as any
        custom data)

CAVEATS
    Some tokens are unavailable to be used as configuration node keys. The
    following keys, as well as any key that is not a valid perl identifier
    <https://perldoc.pl/perldata#Identifier-parsing>, are disallowed - if
    used in a structure file, a warning will be emitted and the applicable
    node will be discarded.

    *   "clone"

    *   "clonePackage"

    *   "destroy"

    *   "DESTROY"

    *   "import"

    *   "new"

    *   "newCore"

    *   "newPackage"

    *   "reflect"

    *   "to_hash"

    *   "get_node"

AUTHOR
    Mark Tyrrell "<mark@tyrrminal.dev>"

LICENSE
    Copyright (c) 2024 Mark Tyrrell

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

