NAME

Concierge::Users::Meta - Field definitions, validators, and configuration utilities for Concierge::Users

VERSION

v0.7.3

SYNOPSIS

use Concierge::Users;

my $users = Concierge::Users->new('/path/to/users-config.json');

# Introspect field schema
my $fields = $users->get_user_fields();     # ordered field list
my $def    = $users->get_field_definition('email');
my $hints  = $users->get_field_hints('email');

# Class-level field lists
my @core = Concierge::Users::Meta::user_core_fields();
my @std  = Concierge::Users::Meta::user_standard_fields();
my @sys  = Concierge::Users::Meta::user_system_fields();

# Display configuration
Concierge::Users::Meta->show_default_config();   # built-in defaults
$users->show_config();                            # active setup

DESCRIPTION

Concierge::Users::Meta is the parent class for Concierge::Users and all storage backends. It owns the master field definitions, the validation subsystem, the filter DSL parser, and the configuration display helpers. Application code normally interacts with Meta indirectly through a Concierge::Users instance, but the introspection methods and class-level field lists are available for direct use.

FIELD CATALOG

Every user record is composed of fields drawn from three built-in categories plus an optional application category.

Core Fields (4)

Always present in every setup.

user_id

Primary authentication identifier.

type:          system
required:      1
max_length:    30
default:       ""
null_value:    ""
must_validate: 0
description:   User login ID - Primary authentication identifier
moniker

User's preferred display name, nickname, or initials.

type:          text
validate_as:   moniker
required:      1
max_length:    24
default:       ""
null_value:    ""
must_validate: 1
description:   User's preferred display name, nickname, or initials
user_status

Account status for access control.

type:          enum
validate_as:   enum
required:      1
options:       *Eligible, OK, Inactive
max_length:    20
default:       Eligible  (auto-set from * option)
null_value:    ""
must_validate: 1

This is a core field (always present), but its options can be replaced via field_overrides to match your application's workflow. See "Field Overrides" for an example.

access_level

Permission level for feature access.

type:          enum
validate_as:   enum
required:      1
options:       *anon, visitor, member, staff, admin
max_length:    20
default:       anon  (auto-set from * option)
null_value:    ""
must_validate: 1

Core field (always present); options can be replaced via field_overrides. See "Field Overrides".

Standard Fields (12)

Included by default when include_standard_fields is omitted or set to 'all'. Pass an arrayref of names to select specific fields, or an empty arrayref [] to exclude all standard fields.

Name fields:

first_name -- type text, validate_as name, max 50, must_validate 1
middle_name -- type text, validate_as name, max 50, must_validate 1
last_name -- type text, validate_as name, max 50, must_validate 1
prefix -- type enum, options: (none) Dr Mr Ms Mrs Mx Prof Hon Sir Madam, max 10
suffix -- type enum, options: (none) Jr Sr II III IV V PhD MD DDS Esq, max 10

Identity fields:

organization -- type text, validate_as text, max 100
title -- type text, validate_as text, max 100

Contact fields:

email -- type email, validate_as email, max 255
phone -- type phone, validate_as phone, max 20
text_ok -- type boolean, validate_as boolean, null_value 0, max 1

Temporal fields:

last_login_date -- type timestamp, validate_as timestamp, default 0000-00-00 00:00:00, max 19
term_ends -- type date, validate_as date, null_value 0000-00-00, max 10

All standard fields have required => 0 by default.

System Fields (2)

Always appended to the field list. Auto-managed by the backends; cannot be set through the public API. Protected from overrides.

last_mod_date -- type system, timestamp updated on every write
created_date -- type system, timestamp set once on creation, required => 1

FIELD ATTRIBUTES

Each field definition is a hashref that may contain the following keys:

field_name -- Internal name (snake_case). Used as hash key and column/file identifier.
category -- One of core, standard, system, or app. Set automatically; protected from overrides.
type -- Data type: text, email, phone, date, timestamp, boolean, integer, enum, system.
validate_as -- Validator to use if different from type. See "VALIDATOR TYPES".
label -- Human-readable label for UI display. Auto-generated from field_name if omitted.
description -- Short explanatory text for documentation or UI hints.
required -- 1 if the field must have a non-null value on creation; 0 otherwise.
must_validate -- 1 if a validation failure should reject the entire operation; 0 to treat failure as a non-fatal warning.
options -- Arrayref of allowed values for enum fields. Prefix one option with * to designate the default (see "Enum Default Convention").
default -- Value assigned to the field on new-record creation when no value is supplied.
null_value -- Sentinel that represents "no data" for this field type (e.g. "" for text, 0 for boolean, 0000-00-00 for date).
max_length -- Maximum character length enforced by the text validator and used as a UI hint.

VALIDATOR TYPES

Ten built-in validators are available. Each is selected by the field's validate_as (or type as fallback) and receives ($user_data, $field_name, $field_def).

text

Validates max_length if defined. Accepts any string.

null_value: ""
email

Pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/

null_value: ""
phone

Digits, spaces, hyphens, parentheses, optional leading +; minimum 7 characters.

null_value: ""
date

Pattern: YYYY-MM-DD (/^\d{4}-\d{2}-\d{2}$/).

null_value: "0000-00-00"
timestamp

Pattern: YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS.

null_value: "0000-00-00 00:00:00"
boolean

Strictly 0 or 1.

null_value: 0
integer

Optional leading minus, digits only (/^\-?\d+$/).

null_value: 0
enum

Value must appear in the field's v_options list (options with * prefix stripped).

null_value: ""
moniker

2-24 alphanumeric characters, no spaces (/^[a-zA-Z0-9]{2,24}$/).

null_value: ""
name

Letters (including accented), hyphens, apostrophes, and internal spaces.

null_value: ""

validate_as vs type

A field's type declares its data type and determines the default validator. validate_as overrides the validator without changing the type. For example, an application field with type => 'text' and validate_as => 'moniker' is stored as text but validated with the moniker pattern. When type is changed via a field override and validate_as is not explicitly set, validate_as is updated automatically to match the new type.

must_validate Behavior

When must_validate is 1 for a field, a validation failure causes the entire register_user or update_user call to return { success => 0 }. When must_validate is 0, the field's value is silently dropped and a warning is appended to the response.

Setting required => 1 in a field override automatically enables must_validate unless must_validate is explicitly set in the same override.

The environment variable USERS_SKIP_VALIDATION bypasses all validation when set to a true value.

FIELD CUSTOMIZATION

Application Fields

Pass app_fields to Concierge::Users->setup() as an arrayref. Each element is either a string (minimal definition) or a hashref (full definition):

app_fields => [
    'nickname',                        # string shorthand
    {                                  # full definition
        field_name  => 'department',
        type        => 'enum',
        options     => ['*Engineering', 'Sales', 'Support'],
        required    => 1,
        label       => 'Department',
    },
],

String shorthand creates a field with type => 'text', validate_as => 'text', required => 0.

Reserved names (any core, standard, or system field name) are rejected with a warning.

Field Overrides

Pass field_overrides to setup() as an arrayref of hashrefs. Each must contain field_name to identify the target:

field_overrides => [
    {
        field_name => 'email',
        required   => 1,
        label      => 'Work Email',
    },
],

Protected fields that cannot be overridden: user_id, created_date, last_mod_date.

Protected attributes that cannot be changed: field_name, category.

Auto-behaviors:

  • Changing type auto-updates validate_as to match (unless validate_as is also specified).

  • Setting required => 1 auto-enables must_validate (unless must_validate is also specified).

  • An unknown validate_as value falls back to text with a warning.

Overriding enum options: Core fields like user_status and access_level are always present, but their options are not fixed. Replace them with values that fit your domain:

# Makerspace member status instead of the default
# Eligible / OK / Inactive
field_overrides => [
    {
        field_name => 'user_status',
        options    => [qw( *Applicant Novice Skilled
                           Expert Mentor Steward )],
    },
],

The *-prefixed option becomes the default (see "Enum Default Convention"). Validation, filtering, and all other enum behaviors apply to the new option set automatically.

Enum Default Convention

In an options arrayref, prefix exactly one value with * to mark it as the default:

options => ['*Free', 'Premium', 'Enterprise']

The * is stripped for validation (stored internally in v_options). If no explicit default is set for the field, the *-marked option becomes the default automatically. A bare * (e.g. in prefix and suffix) represents an empty default.

FILTER DSL

The list_users method accepts a filter string with five operators and two combinators.

Operators

=   exact match             user_status=OK
:   substring (case-insensitive)   last_name:smith
!   not-contains (case-insensitive) email!example.org
>   greater than (string)   last_login_date>2025-01-01
<   less than (string)      term_ends<2026-01-01

Combinators

;   AND -- all conditions must match
|   OR  -- at least one group must match

AND binds tighter than OR: a=1;b=2|c=3 means (a=1 AND b=2) OR (c=3).

Examples

# Active members
user_status=OK;access_level=member

# Staff or admin
access_level=staff|access_level=admin

# Name search with status filter
last_name:Garcia;user_status=OK

# Recent logins
last_login_date>2025-06-01

Unknown fields in a filter string produce a warning and are skipped.

METHODS

Class Methods

user_core_fields

my @fields = Concierge::Users::Meta::user_core_fields();

Returns the list of core field names: user_id, moniker, user_status, access_level.

user_standard_fields

my @fields = Concierge::Users::Meta::user_standard_fields();

Returns the list of standard field names (12 fields).

user_system_fields

my @fields = Concierge::Users::Meta::user_system_fields();

Returns the list of system field names: last_mod_date, created_date.

init_field_meta

my $meta = Concierge::Users::Meta::init_field_meta(\%config);

Processes the setup configuration and returns a hashref with fields (ordered arrayref) and field_definitions (hashref of field definitions). Called internally by Concierge::Users->setup().

show_default_config

Concierge::Users::Meta->show_default_config();

Prints the built-in default field configuration template to STDOUT.

Instance Methods

get_field_definition

my $def = $users->get_field_definition('email');

Returns the complete field definition hashref for the named field, or undef if the field is not in the current schema.

get_field_validator

my $code_ref = $users->get_field_validator('email');

Returns the validator code reference for the named field based on its validate_as or type, or undef if no validator is available.

get_field_hints

my $hints = $users->get_field_hints('email');

Returns a hashref of UI-friendly attributes: label, type, max_length, options, description, required.

get_user_fields

my $fields = $users->get_user_fields();

Returns the ordered arrayref of field names for this instance's schema.

validate_user_data

my $result = $users->validate_user_data(\%data);

Validates %data against the field schema. Returns { success => 1, valid_data => \%clean } on success (with optional warnings arrayref), or { success => 0, message => $reason } on failure.

parse_filter_string

my $filters = $users->parse_filter_string('user_status=OK;access_level=member');

Parses a filter DSL string into an internal structure suitable for backend list methods. See "FILTER DSL".

show_config

$users->show_config();
$users->show_config(output_path => '/tmp/config.yaml');

Prints the active YAML configuration file for this instance to STDOUT. Must be called on a Concierge::Users instance (not a class method).

config_to_yaml

my $yaml = Concierge::Users::Meta::config_to_yaml(\%config, $storage_dir);

Converts a configuration hashref to a human-readable YAML string with a warning header. Used internally during setup().

SEE ALSO

Concierge::Users -- main API and CRUD operations

Concierge::Users::Database, Concierge::Users::File, Concierge::Users::YAML -- storage backend implementations

AUTHOR

Bruce Van Allen <bva@cruzio.com>

LICENSE

This module is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.