Private/Test-IdleADAttributeContract.ps1

function Test-IdleADAttributeContract {
    <#
    .SYNOPSIS
    Validates attributes against the AD Provider attribute contract.

    .DESCRIPTION
    Performs strict validation of provided attributes against the supported attribute contract.
    Throws an exception if unsupported attributes are detected.

    .PARAMETER Attributes
    Hashtable of attributes to validate.

    .PARAMETER Operation
    The operation context: 'CreateIdentity' or 'EnsureAttributes'.

    .PARAMETER AttributeName
    For EnsureAttributes, the specific attribute name being set.

    .OUTPUTS
    System.Collections.Hashtable
    Returns a hashtable with validation results:
    - Requested: array of requested attribute keys
    - Supported: array of supported attribute keys
    - Unsupported: array of unsupported attribute keys

    .EXAMPLE
    $result = Test-IdleADAttributeContract -Attributes $attrs -Operation 'CreateIdentity'
    # Throws if unsupported attributes found

    .EXAMPLE
    $result = Test-IdleADAttributeContract -Operation 'EnsureAttributes' -AttributeName 'MobilePhone'
    # Throws if attribute not supported for EnsureAttributes
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter()]
        [AllowNull()]
        [hashtable] $Attributes,

        [Parameter(Mandatory)]
        [ValidateSet('CreateIdentity', 'EnsureAttributes')]
        [string] $Operation,

        [Parameter()]
        [string] $AttributeName
    )

    $contract = Get-IdleADAttributeContract -Operation $Operation

    if ($Operation -eq 'CreateIdentity') {
        if ($null -eq $Attributes) {
            return @{
                Requested   = @()
                Supported   = @()
                Unsupported = @()
            }
        }

        $requestedKeys = @($Attributes.Keys)
        $supportedKeys = @($contract.Keys)
        $unsupportedKeys = @($requestedKeys | Where-Object { $_ -notin $supportedKeys })

        if ($unsupportedKeys.Count -gt 0) {
            $errorMessage = "AD Provider: Unsupported attributes in CreateIdentity operation.`n"
            $errorMessage += "Unsupported attributes: $($unsupportedKeys -join ', ')`n`n"
            $errorMessage += "Supported attributes for CreateIdentity:`n"
            
            # Generate supported attributes list from contract
            $supportedAttributesList = ($supportedKeys | Sort-Object | ForEach-Object { " - $_" }) -join "`n"
            $errorMessage += "$supportedAttributesList`n`n"
            
            if ('OtherAttributes' -in $supportedKeys) {
                $errorMessage += "To set custom LDAP attributes, use the 'OtherAttributes' container."
            }

            throw $errorMessage
        }

        # Validate OtherAttributes if present
        if ($Attributes.ContainsKey('OtherAttributes')) {
            $otherAttrs = $Attributes['OtherAttributes']
            if ($null -ne $otherAttrs -and $otherAttrs -isnot [hashtable]) {
                throw "AD Provider: 'OtherAttributes' must be a hashtable. Received type: $($otherAttrs.GetType().FullName)"
            }
        }

        return @{
            Requested   = $requestedKeys
            Supported   = @($requestedKeys | Where-Object { $_ -in $supportedKeys })
            Unsupported = $unsupportedKeys
        }
    }
    elseif ($Operation -eq 'EnsureAttributes') {
        if ([string]::IsNullOrWhiteSpace($AttributeName)) {
            throw "AD Provider: AttributeName is required for EnsureAttributes validation."
        }

        # OtherAttributes is a valid container key in EnsureAttributes
        if ($AttributeName -eq 'OtherAttributes') {
            return @{
                Requested   = @($AttributeName)
                Supported   = @($AttributeName)
                Unsupported = @()
            }
        }

        $supportedKeys = @($contract.Keys)

        if ($AttributeName -notin $supportedKeys) {
            $errorMessage = "AD Provider: Unsupported attribute in EnsureAttributes operation.`n"
            $errorMessage += "Attribute: $AttributeName`n`n"
            $errorMessage += "Supported attributes for EnsureAttributes:`n"
            
            # Generate supported attributes list from contract (exclude OtherAttributes container)
            $namedKeys = @($supportedKeys | Where-Object { $_ -ne 'OtherAttributes' })
            $supportedAttributesList = ($namedKeys | Sort-Object | ForEach-Object { " - $_" }) -join "`n"
            $errorMessage += "$supportedAttributesList`n`n"
            
            $errorMessage += "Note: For custom LDAP attributes not listed above, use the 'OtherAttributes' container`n"
            $errorMessage += "with valid LDAP attribute names as keys (e.g. OtherAttributes = @{ mobile = `$null })."

            throw $errorMessage
        }

        return @{
            Requested   = @($AttributeName)
            Supported   = @($AttributeName)
            Unsupported = @()
        }
    }
}