Domain-Scoped Constraints¶
Domain-scoped constraints allow you to define requirements and relationships that only apply when specific domains are active. This enables sophisticated CLI interfaces where different commands have different validation rules and option requirements.
Constraint Application Scope¶
Global vs Domain-Scoped Constraints¶
class MyTool : Arguments() {
// Global constraint - applies always
val verbose by option("--verbose").bool()
.conflictsWith(::quiet) // Always conflicts
// Domain-scoped constraint - applies only in build domain
val buildDomain by domain("build")
.required(::sourceDir) // Only required in build domain
.conflicts(::cleanFirst, ::incrementalBuild) // Only conflicts in build domain
val quiet by option("--quiet").bool()
val sourceDir by option("--source-dir")
val cleanFirst by option("--clean-first").bool()
val incrementalBuild by option("--incremental").bool()
}
# Global constraint applies everywhere
my-tool build --verbose --quiet
# Error: Options --verbose and --quiet conflict
# Domain constraint only applies in build domain
my-tool build --clean-first --incremental
# Error: Options --clean-first and --incremental conflict in domain 'build'
my-tool test --clean-first --incremental
# OK: No conflict outside build domain
Basic Domain Requirements¶
Required Options¶
Make options required only when specific domains are active:
class DatabaseTool : Arguments() {
val migrateDomain by domain("migrate")
.required(::connectionString)
.required(::migrationPath)
val backupDomain by domain("backup")
.required(::connectionString)
.required(::outputFile)
val testDomain by domain("test")
.required(::testDatabase)
val connectionString by option("--connection-string")
val migrationPath by option("--migration-path")
val outputFile by option("--output-file")
val testDatabase by option("--test-database")
}
Minimum Occurrence Requirements¶
class TestRunner : Arguments() {
val unitDomain by domain("unit")
.atLeast(::testFile, 1) // At least one test file
val integrationDomain by domain("integration")
.atLeast(::service, 2) // At least two services
.atLeast(::testSuite, 1)
val testFile by option("--test-file").list()
val service by option("--service").list()
val testSuite by option("--test-suite").list()
}
Conditional Requirements¶
Require If Any Present¶
Require an option when any of the specified trigger options are present in the domain:
class DeployTool : Arguments() {
val prodDomain by domain("production")
.requireIfAnyPresent(::backupLocation, ::database, ::fileStorage)
.requireIfAnyPresent(::approvalToken, ::autoApprove, ::forceDeployment)
val devDomain by domain("development")
.requireIfAnyPresent(::debugPort, ::verbose, ::profileMode)
val backupLocation by option("--backup-location")
val database by option("--database").bool()
val fileStorage by option("--file-storage").bool()
val approvalToken by option("--approval-token").hidden()
val autoApprove by option("--auto-approve").bool()
val forceDeployment by option("--force").bool()
val debugPort by option("--debug-port").int()
val verbose by option("--verbose").bool()
val profileMode by option("--profile").bool()
}
Require If All Present¶
Require an option when all specified trigger options are present:
class SecurityTool : Arguments() {
val encryptDomain by domain("encrypt")
.requireIfAllPresent(::certificate, ::sslMode, ::keyFile) // Need cert password when all SSL components present
val signDomain by domain("sign")
.requireIfAllPresent(::privateKey, ::certificate, ::timestampServer) // Need key password for complete signing
val certificate by option("--certificate")
val sslMode by option("--ssl-mode").oneOf("require", "verify-ca", "verify-full")
val keyFile by option("--key-file")
val certPassword by option("--cert-password").password().hidden()
val privateKey by option("--private-key")
val timestampServer by option("--timestamp-server")
val keyPassword by option("--key-password").password().hidden()
}
Require If Value¶
Require options based on the value of another option within the domain:
class CIDomain : Arguments() {
val deployDomain by domain("deploy")
.requireIfValue(::environment, ::approvalToken) { it == "production" }
.requireIfValue(::environment, ::rollbackPlan) { it in listOf("staging", "production") }
.requireIfValue(::deployMode, ::blueGreenConfig) { it == "blue-green" }
.requireIfValue(::deployMode, ::canaryConfig) { it == "canary" }
val testDomain by domain("test")
.requireIfValue(::testType, ::performanceConfig) { it == "performance" }
.requireIfValue(::testType, ::loadTestConfig) { it == "load" }
val environment by option("--environment").oneOf("dev", "staging", "production")
val approvalToken by option("--approval-token").hidden()
val rollbackPlan by option("--rollback-plan")
val deployMode by option("--deploy-mode").oneOf("rolling", "blue-green", "canary")
val blueGreenConfig by option("--blue-green-config")
val canaryConfig by option("--canary-config")
val testType by option("--test-type").oneOf("unit", "integration", "performance", "load")
val performanceConfig by option("--performance-config")
val loadTestConfig by option("--load-test-config")
}
Group Constraints¶
Exactly One¶
Require exactly one option from a group within the domain:
class BuildTool : Arguments() {
val buildDomain by domain("build")
.exactlyOne(::sourceDir, ::projectFile, ::buildScript) // Exactly one build source
.exactlyOne(::debugBuild, ::releaseBuild, ::profileBuild) // Exactly one build type
val packageDomain by domain("package")
.exactlyOne(::jarPackage, ::nativePackage, ::dockerPackage) // Exactly one package type
val sourceDir by option("--source-dir")
val projectFile by option("--project-file")
val buildScript by option("--build-script")
val debugBuild by option("--debug").bool()
val releaseBuild by option("--release").bool()
val profileBuild by option("--profile").bool()
val jarPackage by option("--jar").bool()
val nativePackage by option("--native").bool()
val dockerPackage by option("--docker").bool()
}
At Most One¶
Allow at most one option from a group within the domain:
class LoggingTool : Arguments() {
val runDomain by domain("run")
.atMostOne(::verbose, ::quiet, ::silent) // Only one verbosity level
.atMostOne(::logFile, ::syslog, ::journald) // At most one log destination
val analysisDomain by domain("analyze")
.atMostOne(::realTime, ::batch) // Processing mode
.atMostOne(::csvOutput, ::jsonOutput, ::xmlOutput) // Output format
val verbose by option("--verbose").bool()
val quiet by option("--quiet").bool()
val silent by option("--silent").bool()
val logFile by option("--log-file")
val syslog by option("--syslog").bool()
val journald by option("--journald").bool()
val realTime by option("--real-time").bool()
val batch by option("--batch").bool()
val csvOutput by option("--csv").bool()
val jsonOutput by option("--json").bool()
val xmlOutput by option("--xml").bool()
}
At Least One¶
Require at least one option from a group within the domain:
class MonitoringTool : Arguments() {
val monitorDomain by domain("monitor")
.atLeastOne(::cpuMetrics, ::memoryMetrics, ::diskMetrics, ::networkMetrics) // Need some metrics
.atLeastOne(::alertEmail, ::alertWebhook, ::alertSms) // Need some alerting
val backupDomain by domain("backup")
.atLeastOne(::localBackup, ::s3Backup, ::gcsBackup, ::azureBackup) // Need backup destination
val cpuMetrics by option("--cpu").bool()
val memoryMetrics by option("--memory").bool()
val diskMetrics by option("--disk").bool()
val networkMetrics by option("--network").bool()
val alertEmail by option("--alert-email")
val alertWebhook by option("--alert-webhook")
val alertSms by option("--alert-sms")
val localBackup by option("--local-backup").bool()
val s3Backup by option("--s3-backup").bool()
val gcsBackup by option("--gcs-backup").bool()
val azureBackup by option("--azure-backup").bool()
}
Conflict Constraints¶
Basic Conflicts¶
Prevent options from being used together within a domain:
class ServerTool : Arguments() {
val startDomain by domain("start")
.conflicts(::developmentMode, ::productionMode) // Can't be both dev and prod
.conflicts(::debugEnabled, ::optimizedBuild) // Debug conflicts with optimization
val testDomain by domain("test")
.conflicts(::fastTests, ::comprehensiveTests) // Different test modes
.conflicts(::mockDatabase, ::realDatabase) // Different database modes
val developmentMode by option("--development").bool()
val productionMode by option("--production").bool()
val debugEnabled by option("--debug").bool()
val optimizedBuild by option("--optimized").bool()
val fastTests by option("--fast").bool()
val comprehensiveTests by option("--comprehensive").bool()
val mockDatabase by option("--mock-db").bool()
val realDatabase by option("--real-db").bool()
}
Multi-Option Conflicts¶
class DeploymentTool : Arguments() {
val deployDomain by domain("deploy")
.conflicts(::dryRun, ::autoApprove, ::forceDeployment) // Can't combine these safety options
.conflicts(::rollback, ::blueGreen, ::canaryDeployment) // Different deployment strategies
val maintenanceDomain by domain("maintenance")
.conflicts(::startMaintenance, ::endMaintenance, ::extendMaintenance) // Only one maintenance action
val dryRun by option("--dry-run").bool()
val autoApprove by option("--auto-approve").bool()
val forceDeployment by option("--force").bool()
val rollback by option("--rollback").bool()
val blueGreen by option("--blue-green").bool()
val canaryDeployment by option("--canary").bool()
val startMaintenance by option("--start-maintenance").bool()
val endMaintenance by option("--end-maintenance").bool()
val extendMaintenance by option("--extend-maintenance").bool()
}
Complex Constraint Examples¶
Multi-Environment Pipeline¶
class PipelineTool : Arguments() {
val devDomain by domain("dev")
.atMostOne(::fastBuild, ::debugBuild)
.conflicts(::requireApproval)
.requireIfValue(::testLevel) { it == "integration" }
val stagingDomain by domain("staging")
.required(::testResults)
.exactlyOne(::promoteToProduction, ::holdForApproval)
.requireIfAnyPresent(::promoteToProduction, ::approvalToken)
.conflicts(::skipTests)
val productionDomain by domain("production")
.required(::approvalToken)
.required(::rollbackPlan)
.exactlyOne(::blueGreenDeploy, ::canaryDeploy, ::rollingDeploy)
.requireIfValue(::blueGreenDeploy, ::loadBalancerConfig) { it == true }
.requireIfValue(::canaryDeploy, ::canaryConfig) { it == true }
.conflicts(::skipValidation, ::skipBackup)
val fastBuild by option("--fast-build").bool()
val debugBuild by option("--debug-build").bool()
val requireApproval by option("--require-approval").bool()
val testLevel by option("--test-level").oneOf("unit", "integration", "e2e")
val testResults by option("--test-results")
val promoteToProduction by option("--promote").bool()
val holdForApproval by option("--hold").bool()
val approvalToken by option("--approval-token").hidden()
val skipTests by option("--skip-tests").bool()
val rollbackPlan by option("--rollback-plan")
val blueGreenDeploy by option("--blue-green").bool()
val canaryDeploy by option("--canary").bool()
val rollingDeploy by option("--rolling").bool()
val loadBalancerConfig by option("--load-balancer-config")
val canaryConfig by option("--canary-config")
val skipValidation by option("--skip-validation").bool()
val skipBackup by option("--skip-backup").bool()
}
Microservice Management¶
class MicroserviceTool : Arguments() {
// Authentication fragment - inherited by multiple domains
val authFragment by domain(fragment = true)
.exactlyOne(::apiKey, ::serviceAccount, ::userAuth)
.requireIfValue(::apiKey, ::region) { it != null }
// Resource management fragment
val resourceFragment by domain(fragment = true)
.required(::cluster)
.atMostOne(::namespace, ::allNamespaces)
.requireIfAnyPresent(::allNamespaces, ::clusterAdmin)
val deployDomain by domain("deploy")
.inherits(::authFragment, ::resourceFragment)
.required(::serviceConfig)
.exactlyOne(::deploymentMode)
.requireIfValue(::deploymentMode, ::canarySettings) { it == "canary" }
.requireIfValue(::deploymentMode, ::blueGreenSettings) { it == "blue-green" }
.conflicts(::dryRun, ::autoConfirm)
val scaleDomain by domain("scale")
.inherits(::authFragment, ::resourceFragment)
.required(::serviceName)
.exactlyOne(::targetReplicas, ::autoscaleConfig)
.requireIfAnyPresent(::autoscaleConfig, ::minReplicas, ::maxReplicas)
val monitorDomain by domain("monitor")
.inherits(::authFragment, ::resourceFragment)
.atLeastOne(::metrics, ::logs, ::events)
.requireIfAnyPresent(::metrics, ::metricsConfig)
.atMostOne(::realTime, ::historical)
// Authentication options
val apiKey by option("--api-key").hidden()
val serviceAccount by option("--service-account")
val userAuth by option("--user-auth").bool()
val region by option("--region")
// Resource options
val cluster by option("--cluster")
val namespace by option("--namespace")
val allNamespaces by option("--all-namespaces").bool()
val clusterAdmin by option("--cluster-admin").bool()
// Deployment options
val serviceConfig by option("--service-config")
val deploymentMode by option("--deployment-mode").oneOf("rolling", "canary", "blue-green")
val canarySettings by option("--canary-settings")
val blueGreenSettings by option("--blue-green-settings")
val dryRun by option("--dry-run").bool()
val autoConfirm by option("--auto-confirm").bool()
// Scaling options
val serviceName by option("--service-name")
val targetReplicas by option("--target-replicas").int()
val autoscaleConfig by option("--autoscale-config")
val minReplicas by option("--min-replicas").int()
val maxReplicas by option("--max-replicas").int()
// Monitoring options
val metrics by option("--metrics").bool()
val logs by option("--logs").bool()
val events by option("--events").bool()
val metricsConfig by option("--metrics-config")
val realTime by option("--real-time").bool()
val historical by option("--historical").bool()
}
Error Messages¶
Domain-scoped constraints provide clear, context-aware error messages:
Requirement Errors¶
my-tool deploy
# Error: Option --service-config is required when domain 'deploy' is active
my-tool scale --autoscale-config config.yml
# Error: Options --min-replicas and --max-replicas are required when --autoscale-config is present in domain 'scale'
Group Constraint Errors¶
my-tool deploy --canary --blue-green
# Error: Exactly one of --deployment-mode must be specified in domain 'deploy'
my-tool monitor
# Error: At least one of --metrics, --logs, --events must be specified in domain 'monitor'
Conflict Errors¶
my-tool deploy --dry-run --auto-confirm
# Error: Options --dry-run and --auto-confirm conflict in domain 'deploy'
my-tool production --skip-validation --skip-backup
# Error: Options --skip-validation and --skip-backup conflict in domain 'production'
Inheritance Errors¶
my-tool deploy --api-key key123
# Error: Option --region is required when --api-key is present (inherited from authFragment)
my-tool scale --all-namespaces
# Error: Option --cluster-admin is required when --all-namespaces is present (inherited from resourceFragment)
Best Practices¶
1. Design Logical Constraint Groups¶
// Good: Logical groupings that make business sense
val sslDomain by domain("ssl")
.exactlyOne(::certificate, ::letsEncrypt, ::selfSigned) // One cert source
.requireIfAnyPresent(::certificate, ::privateKey) // Cert needs key
// Avoid: Arbitrary constraint combinations
val confusingDomain by domain("confusing")
.exactlyOne(::verbose, ::inputFile) // Unrelated options
2. Use Descriptive Constraint Names¶
// Good: Clear constraint purpose
val deployDomain by domain("deploy")
.required(::environment) // Clear requirement
.conflicts(::dryRun, ::autoApprove) // Clear conflict
// Avoid: Unclear constraint relationships
val deployDomain by domain("deploy")
.required(::option1) // What's option1?
.conflicts(::flag1, ::flag2) // Why do they conflict?
3. Leverage Fragment Inheritance¶
// Good: Reusable constraint patterns
val authFragment by domain(fragment = true)
.exactlyOne(::apiKey, ::userAuth, ::serviceAccount)
val deployDomain by domain("deploy").inherits(::authFragment)
val monitorDomain by domain("monitor").inherits(::authFragment)
// Avoid: Duplicating constraints across domains
4. Provide Clear Error Context¶
Domain-scoped constraints automatically provide context about which domain is active and which constraints are violated, making errors easy to understand and fix.
Domain-scoped constraints enable sophisticated CLI applications that can enforce different rules for different commands while maintaining clear, understandable error messages and help text.