Unions and polymorphism
Typra supports closed string unions, open string unions, and discriminated model hierarchies.
Closed string unions
Section titled “Closed string unions”@parseAlias("ready", #["complete"])union FixtureStatus { draft: "draft"; ready: "ready"; archived: "archived";}@parseAlias is parse-only. Loading accepts complete, but saving emits the canonical TypeSpec value ready.
Open string unions
Section titled “Open string unions”@parseAlias("batch", #["bulk"])union FixtureMode { interactive: "interactive"; batch: "batch"; custom: string;}Open unions preserve known values while allowing target-specific handling for custom strings.
Discriminated models
Section titled “Discriminated models”@discriminator("kind")model FixtureContent { kind: string;}
model TextContent extends FixtureContent { kind: "text"; text: string;}
model ImageContent extends FixtureContent { kind: "image"; url: string;}Typra uses the discriminator field to load and save the right child shape. In the fixture contract, FixtureRoot.content and FixtureRoot.contentItems exercise both a single polymorphic value and a polymorphic collection.
Generated shape
Section titled “Generated shape”export type FixtureMode = "interactive" | "batch" | (string & {});export type FixtureStatus = "draft" | "ready" | "archived";
function parseFixtureStatus(value: unknown): FixtureStatus { switch (String(value)) { case "complete": return "ready"; default: throw new Error(`Invalid FixtureStatus value: ${value}`); }}
private static loadKind(data: Record<string, unknown>, context?: LoadContext): FixtureContent { switch (String(data["kind"]).toLowerCase()) { case "text": return TextContent.load(data, context); case "image": return ImageContent.load(data, context); default: return new FixtureContent(); }}FixtureMode = Literal["interactive", "batch"] | strFixtureStatus = Literal["draft", "ready", "archived"]
def _parse_fixture_status(value: Any) -> FixtureStatus: aliases = {"draft": "draft", "ready": "ready", "archived": "archived", "complete": "ready"} if str(value) in aliases: return aliases[str(value)] raise ValueError(f"Invalid FixtureStatus value: {value}")
class FixtureContent: @staticmethod def load(data: Any, context: LoadContext | None = None) -> "FixtureContent": if data.get("kind") == "text": return TextContent.load(data, context) if data.get("kind") == "image": return ImageContent.load(data, context) return FixtureContent()public enum FixtureStatus{ Draft, Ready, Archived}
public static class FixtureStatusParser{ public static FixtureStatus Parse(string value) => value switch { "complete" => FixtureStatus.Ready, _ => Enum.Parse<FixtureStatus>(value, true) };}
return discriminator switch{ "text" => TextContent.Load(data, context), "image" => ImageContent.Load(data, context), _ => new FixtureContent(),};func ParseFixtureStatus(value string) (FixtureStatus, error) { switch value { case "ready", "complete": return FixtureStatusReady, nil default: return "", fmt.Errorf("invalid FixtureStatus value: %s", value) }}
func LoadFixtureContent(data interface{}, ctx *LoadContext) (interface{}, error) { if m, ok := data.(map[string]interface{}); ok { switch m["kind"] { case "text": return LoadTextContent(data, ctx) case "image": return LoadImageContent(data, ctx) } } return FixtureContent{}, nil}enum FixtureStatus { DRAFT("draft"), READY("ready"), ARCHIVED("archived");
public static FixtureStatus fromValue(String value) { if (value.equals("complete")) return READY; for (FixtureStatus item : values()) { if (item.value.equals(value)) return item; } return valueOf(value.toUpperCase().replace("-", "_")); }}
if ("text".equals(map.get("kind"))) return TextContent.load(input, ctx);if ("image".equals(map.get("kind"))) return ImageContent.load(input, ctx);pub enum FixtureMode { Interactive, Batch, Other(String),}
impl FixtureStatus { pub fn from_str_ignore_case_opt(s: &str) -> Option<Self> { if s.eq_ignore_ascii_case("complete") { return Some(Self::Ready); } None }}
pub enum FixtureContent { TextContent(TextContent), ImageContent(ImageContent),}public enum FixtureStatus: String, Codable, CaseIterable { case draft, ready, archived public static func parse(_ value: String) throws -> FixtureStatus { switch value { case "draft": return .draft case "ready", "complete": return .ready case "archived": return .archived default: throw TypraRuntimeError.invalidEnum(type: "FixtureStatus", value: value) } }}
public struct FixtureMode: RawRepresentable, Equatable, Hashable, Codable { public let rawValue: String public static let interactive = FixtureMode(rawValue: "interactive") public static let batch = FixtureMode(rawValue: "batch") public static func parse(_ value: String) throws -> FixtureMode { switch value { case "bulk": return .batch default: return FixtureMode(rawValue: value) } }}
public enum FixtureContent: TypraModel { case textContent(TextContent) case imageContent(ImageContent) case unknown([String: Any])
public static func load(_ data: Any, context: LoadContext = LoadContext()) throws -> FixtureContent { let object = try TypraRuntime.object(data, typeName: "FixtureContent") switch try TypraRuntime.string(object["kind"] ?? "", field: "kind") { case "text": return .textContent(try TextContent.load(data, context: context)) case "image": return .imageContent(try ImageContent.load(data, context: context)) default: return .unknown(object) } }}{ "unions": { "FixtureStatus": { "values": ["draft", "ready", "archived"], "aliases": { "complete": "ready" } }, "FixtureMode": { "values": ["interactive", "batch"], "open": true, "aliases": { "bulk": "batch" } } }, "discriminators": { "FixtureContent": { "property": "kind", "children": { "text": "TextContent", "image": "ImageContent" } } }}