PolicyFlow

The Type System

Understanding PolicyFlow's powerful type system

PolicyFlow features a strong, static type system that provides safety, clarity, and powerful IDE support. This guide covers all the types available in PolicyFlow and how to use them effectively.

Built-in Types Availability

All built-in types and libraries in PolicyFlow are globally available without requiring imports. This includes:

  • All primitive types (String, Number, Decimal, Boolean)
  • All temporal types (DateTime, Date, Time, Duration)
  • All specialized types (Email, URL, IPAddress, UUID)
  • All built-in libraries (Math, Crypto, Runtime, Relationships)

Primitive Types

Basic Types

TypeDescriptionExample
StringUnicode text"Hello, World!"
Number64-bit signed integer42, -100, 1_000_000
DecimalHigh-precision decimal (floating-point)99.99, 0.0001
BooleanTrue or falsetrue, false

Number vs Decimal

  • Number: Use for counting, IDs, array indices, and any whole number values
age: Number              // 25
count: Number            // 1000
priority: Number         // 1, 2, 3
  • Decimal: Use for monetary values, percentages, measurements, and any fractional values
price: Decimal           // 99.99
taxRate: Decimal         // 0.08
percentage: Decimal      // 0.15

Temporal Types

TypeDescriptionExample
DateTimeISO 8601 timestamp with timezoneDateTime.Parse("2025-06-12T12:00:00Z")
DateDate without timeDate.Today()
TimeTime without dateTime.Parse("14:30:00")
DurationTime spanDuration.FromUnit(TimeUnit.Hours, 2)

Specialized String Types

These types provide built-in validation and specialized methods:

TypeDescriptionExample
EmailRFC-compliant email address"user@example.com"
URLValid URL"https://example.com/path"
IPAddressIPv4 or IPv6 address"192.168.1.1", "::1"
UUIDUniversally unique identifier"550e8400-e29b-41d4-a716-446655440000"

Composite Types

Arrays

Ordered collections of elements of the same type:

schema ExampleSchema {
    type User {
        roles: String[]           // Array of strings
        loginTimes: DateTime[]    // Array of timestamps
        scores: Number[]          // Array of numbers
    }
}

Maps

Key-value dictionaries with typed keys and values:

schema ExampleSchema {
    type User {
        attributes: Map<String, Any>      // Flexible attributes
        permissions: Map<String, Boolean> // Permission flags
        metadata: Map<String, String>     // String key-value pairs
    }
}

Optional Types

Any type can be made nullable with the ? modifier:

schema ExampleSchema {
    type User {
        email: Email            // Required
        phoneNumber: String?    // Optional (can be null)
        manager: User?          // Optional reference
    }
}

Object Types

The Object Type Concept

The Object type serves as the conceptual parent for all complex types. It's an abstract concept and cannot be used directly as a concrete type.

schema InvalidExample {
    type User {
        // ERROR: Cannot use 'Object' as a concrete type
        data: Object  // ❌ This will not compile
    }
}

Instead, define specific types:

schema ValidExample {
    type UserData {
        name: String
        age: Number
    }

    type User {
        data: UserData  // ✅ Use a defined type
    }
}

Type Inheritance

Types can inherit from other types to build complex, reusable data models:

schema BaseSchema {
    // Base type with common fields
    type Entity {
        id: UUID
        createdAt: DateTime
        updatedAt: DateTime
    }

    // Another base type
    type Auditable : Entity {
        createdBy: UUID
        lastModifiedBy: UUID
    }
}
import * as Base from "@/schemas/base.pfs";

schema UserSchema {
    // Inherits id, createdAt, updatedAt from Entity
    User type CorporateUser : Base.Entity {
        email: Email
        department: String
    }

    // Inherits from Auditable (which inherits from Entity)
    Resource type SensitiveDocument : Base.Auditable {
        content: String
        classification: String
    }
}

Type Categories

Regular Types

Standard data structures that define the shape of data:

schema DataSchema {
    // Regular types - not directly usable in policies
    type Address {
        street: String
        city: String
        country: String
    }

    type PhoneNumber {
        countryCode: String
        number: String
    }
}

Designated Types

Types marked with User, Resource, Context, or Relationship can be used in policy schemas. The correct syntax is:

schema PolicySchema {
    // Correct syntax: designation keyword before 'type'
    User type Employee {
        id: UUID
        email: Email
        roles: String[]
    }

    // Can be used in 'Resource from' in policies
    Resource type Document {
        id: UUID
        ownerId: UUID
        content: String
    }

    // Can be used in 'Context from' in policies
    Context type WebRequest {
        ipAddress: IPAddress
        userAgent: String
    }

    // Can be used to define relationship schemas with attributes
    Relationship type TeamMembership {
        userId: UUID
        teamId: UUID
        role: String
        permissions: String[]
        since: DateTime
        active: Boolean = true
    }
}

The four designated type categories are:

  • User: Represents the principal (person, service, or entity) performing an action
  • Resource: Represents the object being accessed or operated on
  • Context: Represents the environmental conditions of the request (IP, time, etc.)
  • Relationship: Defines the schema for relationship attributes in the relationship graph

Working with Any Type

The Any type provides flexibility for dynamic data, but requires special handling:

// Schema with dynamic attributes
schema FlexibleSchema {
    User type FlexibleUser {
        id: UUID
        attributes: Map<String, Any>  // Can hold any value type
    }
}
// Using Any type safely in policies
policy FlexiblePolicy {
    rule CheckDynamicAttribute {
        when {
            // Use Runtime library for safe access
            if (Runtime.HasProperty(user.attributes, "department")) {
                const dept = Runtime.GetProperty(user.attributes, "department");
                return dept == "Engineering";
            }
            return false;
        }
        then ALLOW
    }
}

Type Constraints

Range Constraints

Numeric types can have range constraints that are validated when values are assigned:

schema ConstrainedSchema {
    type User {
        age: Number range(0..120)
        score: Decimal range(0.0..100.0)
        priority: Number range(1..5)
    }
}

Example usage in policies:

policy AgeRestrictedAccess {
    rules {
        rule AdultContent {
            when user.age >= 18  // age is guaranteed to be 0-120
            then ALLOW
        }

        rule PriorityAccess {
            when user.priority >= 3  // priority is guaranteed to be 1-5
            then ALLOW
        }
    }
}

String Pattern Constraints

String types can be constrained with regular expressions using the pattern() constraint:

schema PatternSchema {
    type User {
        username: String pattern("^[a-zA-Z0-9_]+$")
        phoneNumber: String pattern("^\\+?[1-9]\\d{1,14}$")
        employeeId: String pattern("^EMP-\\d{6}$")
    }
}

Example usage:

policy ValidatedAccess {
    rules {
        rule EmployeeOnly {
            // employeeId is guaranteed to match EMP-XXXXXX format
            when user.employeeId.StartsWith("EMP-")
            then ALLOW
        }
    }
}

Default Values

Types can have default values that are evaluated at object instantiation time:

schema DefaultSchema {
    type User {
        isActive: Boolean = true
        createdAt: DateTime = DateTime.Now()  // Set when object is created
        role: String = "user"
        tags: String[] = []
        score: Decimal = 0.0
    }
}

Important: Default values are evaluated when the object is created, not when the schema is defined:

// If two users are created at different times:
// User A created at 2025-06-12 10:00:00
// User B created at 2025-06-12 10:00:05
// They will have different createdAt values

policy CheckNewUsers {
    rules {
        rule NewUserRestrictions {
            when {
                // Each user's createdAt reflects when they were instantiated
                const accountAge = (DateTime.Now() - user.createdAt).TotalHours();
                return accountAge < 24;  // Less than 24 hours old
            }
            then DENY
            reason: "New accounts have restricted access for 24 hours"
        }
    }
}

Another example showing different default timestamps:

policy AuditDefaultValues {
    rules {
        rule LogCreationTime {
            when resource.createdAt != null
            then ALLOW
        }
    }
}

Type Methods

Every type instance has access to specific methods. Here are some key examples:

Universal Method

All object instances have:

  • .ToString() - Returns a string representation

String Methods

  • .Length(), .Lower(), .Upper(), .Trim()
  • .Contains(), .StartsWith(), .EndsWith()
  • .Matches(), .Split()

Collection Methods

  • .Count(), .IsEmpty()
  • .Contains(), .Distinct()
  • .Any(), .All(), .Filter(), .Map()

Specialized Type Methods

  • Email: .Domain(), .LocalPart()
  • URL: .Host(), .Path(), .GetQueryParam()
  • IPAddress: .IsIPv4(), .IsIPv6(), .Matches(cidr)

For a complete reference of all type methods, see the Type Methods Reference.

Next Steps