Skip to content

Value parsers

Value parsers are the specialized components responsible for converting raw string input from command-line arguments into strongly-typed values. While command-line arguments are always strings, your application needs them as numbers, URLs, file paths, or other typed values. Value parsers handle this critical transformation and provide validation at parse time.

The philosophy behind Optique's value parsers is “fail fast, fail clearly.” Rather than letting invalid data flow through your application and cause mysterious errors later, value parsers validate input immediately when parsing occurs. When validation fails, they provide clear, actionable error messages that help users understand what went wrong and how to fix it.

Every value parser implements the ValueParser<T> interface, which defines how to parse strings into values of type T and how to format those values back into strings for help text. This consistent interface makes value parsers composable and allows you to create custom parsers that integrate seamlessly with Optique's type system.

string() parser

The string() parser is the most basic value parser—it accepts any string input and performs optional pattern validation. While all command-line arguments start as strings, the string() parser provides a way to explicitly document that you want string values and optionally validate their format.

typescript
import { 
string
} from "@optique/core/valueparser";
// Basic string parser const
name
=
string
();
// String with custom metavar for help text const
hostname
=
string
({
metavar
: "HOST" });
// String with pattern validation const
identifier
=
string
({
metavar
: "ID",
pattern
: /^[a-zA-Z][a-zA-Z0-9_]*$/
});

Pattern validation

The optional pattern parameter accepts a regular expression that the input must match:

typescript
// Email-like pattern
const 
email
=
string
({
pattern
: /^[^@]+@[^@]+\.[^@]+$/,
metavar
: "EMAIL"
}); // Semantic version pattern const
version
=
string
({
pattern
: /^\d+\.\d+\.\d+$/,
metavar
: "VERSION"
});

When pattern validation fails, the parser provides a clear error message indicating what pattern was expected:

bash
$ myapp --version 1.2
Error: Expected a string matching pattern ^\d+\.\d+\.\d+$, but got 1.2.

The string() parser uses "STRING" as its default metavar, which appears in help text to indicate what kind of input is expected.

integer() parser

The integer() parser converts string input to numeric values with validation. It supports both regular JavaScript numbers (safe up to Number.MAX_SAFE_INTEGER) and arbitrary-precision bigint values for very large integers.

Number mode (default)

By default, integer() returns JavaScript numbers and validates that input contains only digits:

typescript
import { 
integer
} from "@optique/core/valueparser";
// Basic integer const
count
=
integer
();
// Integer with bounds checking const
port
=
integer
({
min
: 1,
max
: 65535 });
// Integer with custom metavar const
timeout
=
integer
({
min
: 0,
metavar
: "SECONDS" });

Bigint mode

For very large integers that exceed JavaScript's safe integer range, specify type: "bigint":

typescript
// BigInt integer with bounds
const 
largeNumber
=
integer
({
type
: "bigint",
min
: 0n,
max
: 999999999999999999999n
});

Validation and error messages

The integer() parser provides detailed validation:

  • Format validation: Ensures input contains only digits
  • Range validation: Enforces minimum and maximum bounds
  • Overflow protection: Prevents values outside safe ranges
bash
$ myapp --port "abc"
Error: Expected a valid integer, but got abc.

$ myapp --port "99999"
Error: Expected a value less than or equal to 65,535, but got 99999.

The parser uses "INTEGER" as its default metavar.

float() parser

The float() parser handles floating-point numbers with comprehensive validation options. It recognizes standard decimal notation, scientific notation, and optionally special values like NaN and Infinity.

typescript
import { 
float
} from "@optique/core/valueparser";
// Basic float const
rate
=
float
();
// Float with bounds const
percentage
=
float
({
min
: 0.0,
max
: 100.0 });
// Float allowing special values const
scientific
=
float
({
allowNaN
: true,
allowInfinity
: true,
metavar
: "NUMBER"
});

Supported formats

The float() parser recognizes multiple numeric formats:

  • Integers: 42, -17
  • Decimals: 3.14, -2.5, .5, 42.
  • Scientific notation: 1.23e10, 5.67E-4, 1e+5
  • Special values: NaN, Infinity, -Infinity (when allowed)

Special values

By default, NaN and Infinity are not allowed, which prevents accidental acceptance of these values. Enable them explicitly when they're meaningful for your use case:

typescript
// Mathematics application that handles infinite limits
const 
limit
=
float
({
allowInfinity
: true,
metavar
: "LIMIT"
}); // Statistical application that handles missing data const
value
=
float
({
allowNaN
: true,
allowInfinity
: true,
metavar
: "VALUE"
});

The parser uses "NUMBER" as its default metavar and provides clear error messages for invalid formats and out-of-range values.

choice() parser

The choice() parser creates type-safe enumerations by restricting input to one of several predefined string values. This is perfect for options like log levels, output formats, or operation modes where only specific values make sense.

typescript
import { 
choice
} from "@optique/core/valueparser";
// Log level choice const
logLevel
=
choice
(["debug", "info", "warn", "error"]);
// Output format choice const
format
=
choice
(["json", "yaml", "xml", "csv"]);
// Case-insensitive choice const
protocol
=
choice
(["http", "https", "ftp"], {
caseInsensitive
: true
});

Type-safe string literals

The choice() parser creates exact string literal types rather than generic string types, providing excellent TypeScript integration:

typescript
const 
level
=
choice
(["debug", "info", "warn", "error"]);

Case sensitivity

By default, matching is case-sensitive. Set caseInsensitive: true to accept variations:

typescript
const 
format
=
choice
(["JSON", "XML"], {
caseInsensitive
: true,
metavar
: "FORMAT"
}); // Accepts: "json", "JSON", "Json", "xml", "XML", "Xml"

Error messages

When invalid choices are provided, the parser lists all valid options:

bash
$ myapp --format "txt"
Error: Expected one of json, yaml, xml, csv, but got txt.

The parser uses "TYPE" as its default metavar.

url() parser

The url() parser validates that input is a well-formed URL and optionally restricts the allowed protocols. The parsed result is a JavaScript URL object with all the benefits of the native URL API.

typescript
import { 
url
} from "@optique/core/valueparser";
// Basic URL parser const
endpoint
=
url
();
// URL with protocol restrictions const
apiUrl
=
url
({
allowedProtocols
: ["https:"],
metavar
: "HTTPS_URL"
}); // URL allowing multiple protocols const
downloadUrl
=
url
({
allowedProtocols
: ["http:", "https:", "ftp:"],
metavar
: "URL"
});

Protocol validation

The allowedProtocols option restricts which URL schemes are acceptable. Protocol names must include the trailing colon:

typescript
// Only secure protocols
const 
secureUrl
=
url
({
allowedProtocols
: ["https:", "wss:"]
}); // Web protocols only const
webUrl
=
url
({
allowedProtocols
: ["http:", "https:"]
});

URL object benefits

The parser returns native URL objects, providing immediate access to URL components:

typescript
const 
result
=
parse
(
apiUrl
, ["https://api.example.com:8080/v1/users"]);
if (
result
.
success
) {
const
url
=
result
.
value
;
console
.
log
(`Host: ${
url
.
hostname
}`); // "api.example.com"
console
.
log
(`Port: ${
url
.
port
}`); // "8080"
console
.
log
(`Path: ${
url
.
pathname
}`); // "/v1/users"
console
.
log
(`Protocol: ${
url
.
protocol
}`); // "https:"
}

Validation errors

The parser provides specific error messages for different validation failures:

bash
$ myapp --url "not-a-url"
Error: Invalid URL: not-a-url.

$ myapp --url "ftp://example.com"  # when only HTTPS allowed
Error: URL protocol ftp: is not allowed. Allowed protocols: https:.

The parser uses "URL" as its default metavar.

locale() parser

The locale() parser validates locale identifiers according to the Unicode Locale Identifier standard (BCP 47). It returns Intl.Locale objects that provide rich locale information and integrate with JavaScript's internationalization APIs.

typescript
import { 
locale
} from "@optique/core/valueparser";
// Basic locale parser const
userLocale
=
locale
();
// Locale with custom metavar const
displayLocale
=
locale
({
metavar
: "LANG" });

Locale validation

The parser uses the native Intl.Locale constructor for validation, ensuring compliance with international standards:

typescript
// Valid locales
const validLocales = [
  "en",              // Language only
  "en-US",           // Language and region
  "zh-Hans-CN",      // Language, script, and region
  "de-DE-u-co-phonebk" // With Unicode extension
];

// Invalid locales will be rejected
const invalidLocales = [
  "invalid-locale",
  "en_US",           // Underscore instead of hyphen
  "toolong-language-tag-name"
];

Locale object benefits

The parser returns Intl.Locale objects with rich locale information:

typescript
const 
result
=
parse
(
userLocale
, ["zh-Hans-CN"]);
if (
result
.
success
) {
const
locale
=
result
.
value
;
console
.
log
(`Language: ${
locale
.
language
}`); // "zh"
console
.
log
(`Script: ${
locale
.
script
}`); // "Hans"
console
.
log
(`Region: ${
locale
.
region
}`); // "CN"
console
.
log
(`Base name: ${
locale
.
baseName
}`); // "zh-Hans-CN"
}

The parser uses "LOCALE" as its default metavar and provides clear error messages for invalid locale identifiers.

uuid() parser

The uuid() parser validates UUID (Universally Unique Identifier) strings according to the standard format and optionally restricts to specific UUID versions. It returns a branded Uuid string type for additional type safety.

typescript
import { 
uuid
} from "@optique/core/valueparser";
// Basic UUID parser const
id
=
uuid
();
// UUID with version restrictions const
uuidV4
=
uuid
({
allowedVersions
: [4],
metavar
: "UUID_V4"
}); // UUID allowing multiple versions const
trackingId
=
uuid
({
allowedVersions
: [1, 4, 5]
});

UUID format validation

The parser validates the standard UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx where each x is a hexadecimal digit:

# Valid UUID formats
550e8400-e29b-41d4-a716-446655440000  # Version 1
123e4567-e89b-12d3-a456-426614174000  # Version 1
6ba7b810-9dad-11d1-80b4-00c04fd430c8  # Version 1
6ba7b811-9dad-11d1-80b4-00c04fd430c8  # Version 1

# Invalid formats
550e8400-e29b-41d4-a716-44665544000   # Too short
550e8400-e29b-41d4-a716-446655440000x  # Extra character
550e8400e29b41d4a716446655440000       # Missing hyphens

Version validation

When allowedVersions is specified, the parser checks the version digit (character 14) and validates it matches one of the allowed versions:

typescript
const 
uuidV4Only
=
uuid
({
allowedVersions
: [4] });
// This will pass validation (version 4 UUID) const
validV4
= "550e8400-e29b-41d4-a716-446655440000";
// This will fail validation (version 1 UUID) const
invalidV1
= "6ba7b810-9dad-11d1-80b4-00c04fd430c8";

UUID type safety

The parser returns a branded Uuid type rather than a plain string, providing additional compile-time safety:

typescript
type 
Uuid
= `${string}-${string}-${string}-${string}-${string}`;
// The branded type prevents accidental usage of regular strings as UUIDs function
processUuid
(
id
:
Uuid
) {
// Implementation here } const
result
=
parse
(
uuidParser
, ["550e8400-e29b-41d4-a716-446655440000"]);
if (
result
.
success
) {
processUuid
(
result
.
value
); // ✓ Type-safe
} // This would cause a TypeScript error:
processUuid
("not-a-uuid"); // ✗ Type error
Argument of type '"not-a-uuid"' is not assignable to parameter of type '`${string}-${string}-${string}-${string}-${string}`'.

The parser uses "UUID" as its default metavar and provides specific error messages for format violations and version mismatches.

path() parser

The path() parser validates file system paths with comprehensive options for existence checking, type validation, and file extension filtering. Unlike other built-in value parsers, path() is provided by the @optique/run package since it uses Node.js file system APIs.

typescript
import { 
path
} from "@optique/run/valueparser";
// Basic path parser (any path, no validation) const
configPath
=
path
({
metavar
: "CONFIG" });
// File must exist const
inputFile
=
path
({
metavar
: "FILE",
mustExist
: true,
type
: "file" });
// Directory must exist const
outputDir
=
path
({
metavar
: "DIR",
mustExist
: true,
type
: "directory" });
// Config files with specific extensions const
configFile
=
path
({
metavar
: "CONFIG",
mustExist
: true,
type
: "file",
extensions
: [".json", ".yaml", ".yml"]
});

Path validation options

The path() parser accepts comprehensive configuration options:

typescript
interface PathOptions {
  
metavar
?: string; // Custom metavar (default: "PATH")
mustExist
?: boolean; // Path must exist on filesystem (default: false)
type
?: "file" | "directory" | "either"; // Expected path type (default: "either")
allowCreate
?: boolean; // Allow creating new files (default: false)
extensions
?: string[]; // Allowed file extensions (e.g., [".json", ".txt"])
}

Existence validation

When mustExist: true, the parser verifies that the path exists on the file system:

typescript
import { 
path
} from "@optique/run/valueparser";
// Must exist (file or directory) const
existingPath
=
path
({
mustExist
: true });
// Must be an existing file const
existingFile
=
path
({
mustExist
: true,
type
: "file",
metavar
: "INPUT_FILE"
}); // Must be an existing directory const
existingDir
=
path
({
mustExist
: true,
type
: "directory",
metavar
: "OUTPUT_DIR"
});

File creation validation

With allowCreate: true, the parser validates that the parent directory exists for new files:

typescript
import { 
path
} from "@optique/run/valueparser";
// Allow creating new files (parent directory must exist) const
newFile
=
path
({
allowCreate
: true,
type
: "file",
metavar
: "LOG_FILE"
}); // Combination: file can be new or existing const
flexibleFile
=
path
({
type
: "file",
allowCreate
: true,
extensions
: [".log", ".txt"]
});

Extension filtering

Restrict file paths to specific extensions using the extensions option:

typescript
import { 
path
} from "@optique/run/valueparser";
// Configuration files only const
configFile
=
path
({
mustExist
: true,
type
: "file",
extensions
: [".json", ".yaml", ".yml", ".toml"],
metavar
: "CONFIG_FILE"
}); // Image files only const
imageFile
=
path
({
mustExist
: true,
type
: "file",
extensions
: [".jpg", ".jpeg", ".png", ".gif", ".webp"],
metavar
: "IMAGE_FILE"
}); // Script files (existing or new) const
scriptFile
=
path
({
allowCreate
: true,
type
: "file",
extensions
: [".js", ".ts", ".py", ".sh"],
metavar
: "SCRIPT"
});

Error messages

The path() parser provides specific error messages for different validation failures:

bash
$ myapp --config "nonexistent.json"
Error: Path nonexistent.json does not exist.

$ myapp --input "directory_not_file"
Error: Expected a file, but directory_not_file is not a file.

$ myapp --config "config.txt"
Error: Expected file with extension .json, .yaml, .yml, got .txt.

$ myapp --output "new_file/in/nonexistent/dir.txt"
Error: Parent directory new_file/in/nonexistent does not exist.

Creating custom value parsers

When the built-in value parsers don't meet your needs, you can create custom value parsers by implementing the ValueParser<T> interface. Custom parsers integrate seamlessly with Optique's type system and provide the same error handling and help text generation as built-in parsers.

ValueParser interface

The ValueParser<T> interface defines three required properties:

typescript
interface 
ValueParser
<
T
> {
readonly
metavar
: string;
parse
(
input
: string):
ValueParserResult
<
T
>;
format
(
value
:
T
): string;
}
metavar
The placeholder text shown in help messages (usually uppercase)
parse()
Converts string input to typed value or returns error
format()
Converts typed value back to string for display

Basic custom parser

Here's a simple custom parser for IPv4 addresses:

typescript
import { 
message
} from "@optique/core/message";
import type {
ValueParser
,
ValueParserResult
} from "@optique/core/valueparser";
interface IPv4Address {
octets
: [number, number, number, number];
toString
(): string;
} function
ipv4
():
ValueParser
<IPv4Address> {
return {
metavar
: "IP_ADDRESS",
parse
(
input
: string):
ValueParserResult
<IPv4Address> {
const
parts
=
input
.
split
('.');
if (
parts
.
length
!== 4) {
return {
success
: false,
error
:
message
`Expected IPv4 address in format a.b.c.d, but got ${
input
}.`
}; } const
octets
: number[] = [];
for (const
part
of
parts
) {
const
num
=
parseInt
(
part
, 10);
if (
isNaN
(
num
) ||
num
< 0 ||
num
> 255) {
return {
success
: false,
error
:
message
`Invalid IPv4 octet: ${
part
}. Must be 0-255.`
}; }
octets
.
push
(
num
);
} return {
success
: true,
value
: {
octets
:
octets
as [number, number, number, number],
toString
() {
return
octets
.
join
('.');
} } }; },
format
(
value
: IPv4Address): string {
return
value
.
toString
();
} }; }

Parser with options

More sophisticated parsers can accept configuration options:

typescript
import { 
message
} from "@optique/core/message";
import type {
ValueParser
,
ValueParserResult
} from "@optique/core/valueparser";
interface DateParserOptions {
metavar
?: string;
format
?: 'iso' | 'us' | 'eu';
allowFuture
?: boolean;
} function
date
(
options
: DateParserOptions = {}):
ValueParser
<Date> {
const {
metavar
= "DATE",
format
= 'iso',
allowFuture
= true } =
options
;
return {
metavar
,
parse
(
input
: string):
ValueParserResult
<Date> {
let
date
: Date;
// Parse according to format switch (
format
) {
case 'iso':
date
= new
Date
(
input
);
break; case 'us': // MM/DD/YYYY format const
usMatch
=
input
.
match
(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
if (!
usMatch
) {
return {
success
: false,
error
:
message
`Expected US date format MM/DD/YYYY, but got ${
input
}.`
}; }
date
= new
Date
(
parseInt
(
usMatch
[3]),
parseInt
(
usMatch
[1]) - 1,
parseInt
(
usMatch
[2]));
break; case 'eu': // DD/MM/YYYY format const
euMatch
=
input
.
match
(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
if (!
euMatch
) {
return {
success
: false,
error
:
message
`Expected EU date format DD/MM/YYYY, but got ${
input
}.`
}; }
date
= new
Date
(
parseInt
(
euMatch
[3]),
parseInt
(
euMatch
[2]) - 1,
parseInt
(
euMatch
[1]));
break; } // Validate parsed date if (
isNaN
(
date
.
getTime
())) {
return {
success
: false,
error
:
message
`Invalid date: ${
input
}.`
}; } // Check future constraint if (!
allowFuture
&&
date
> new
Date
()) {
return {
success
: false,
error
:
message
`Future dates not allowed, but got ${
input
}.`
}; } return {
success
: true,
value
:
date
};
},
format
(
value
: Date): string {
switch (
format
) {
case 'iso': return
value
.
toISOString
().
split
('T')[0];
case 'us': return `${
value
.
getMonth
() + 1}/${
value
.
getDate
()}/${
value
.
getFullYear
()}`;
case 'eu': return `${
value
.
getDate
()}/${
value
.
getMonth
() + 1}/${
value
.
getFullYear
()}`;
} } }; } // Usage const
birthDate
=
date
({
format
: 'us',
allowFuture
: false,
metavar
: "BIRTH_DATE"
});

Integration with parsers

Custom value parsers work seamlessly with Optique's parser combinators:

typescript
import { 
argument
,
object
,
option
,
parse
} from "@optique/core/parser";
import {
integer
} from "@optique/core/valueparser";
const
serverConfig
=
object
({
address
:
option
("--bind",
ipv4
()),
startDate
:
argument
(
date
({
format
: 'iso' })),
port
:
option
("-p", "--port",
integer
({
min
: 1,
max
: 65535 }))
}); // Full type safety with custom types const
config
=
parse
(
serverConfig
, [
"--bind", "192.168.1.100", "--port", "8080", "2023-12-25" ]); if (
config
.
success
) {
console
.
log
(`Binding to ${
config
.
value
.
address
.
toString
()}:${
config
.
value
.
port
}.`);
console
.
log
(`Start date: ${
config
.
value
.
startDate
.
toDateString
()}.`);
}

Validation best practices

When creating custom value parsers, follow these best practices:

Clear error messages

Provide specific, actionable error messages that help users understand what went wrong:

typescript
// Good: Specific and helpful
return {
  
success
: false,
error
:
message
`Expected IPv4 address in format a.b.c.d, but got ${
input
}.`
}; // Bad: Vague and unhelpful return {
success
: false,
error
:
message
`Invalid input.`
};

Comprehensive validation

Validate all aspects of your input format:

typescript
parse
(
input
: string):
ValueParserResult
<
T
> {
// 1. Format validation if (!
correctFormat
(
input
)) {
return {
success
: false,
error
:
formatError
};
} // 2. Range/constraint validation if (!
withinBounds
(
input
)) {
return {
success
: false,
error
:
boundsError
};
} // 3. Semantic validation if (!
semanticallyValid
(
input
)) {
return {
success
: false,
error
:
semanticError
};
} return {
success
: true,
value
:
parseValue
(
input
) };
}

Consistent metavar naming

Use descriptive, uppercase metavar names that clearly indicate the expected input:

typescript
// Good metavar examples
metavar: "EMAIL"
metavar: "FILE"
metavar: "PORT"
metavar: "UUID"

// Poor metavar examples
metavar: "input"
metavar: "value"
metavar: "thing"

Custom value parsers extend Optique's type safety and validation capabilities to handle domain-specific data types while maintaining consistency with the built-in parsers and providing excellent integration with TypeScript's type system.