You almost had it:
let e = React.string((code :> string))
:> is the coercion operator. It can coerce any variant that’s only represented by strings into a string (or to float, int etc, if that’s what the variant is represented as at runtime).
Unrelated to your actual question, but you can leverage this fact the other way as well if you add a default case to your variant:
@unboxed
type firestoreErrorCode =
| @as("cancelled") Cancelled
| @as("unknown") Unknown
| @as("invalid-argument") InvalidArgument
| @as("deadline-exceeded") DeadlineExceeded
| @as("not-found") NotFound
| @as("already-exists") AlreadyExists
| @as("permission-denied") PermissionDenied
| @as("resource-exhausted") ResourceExhausted
| @as("failed-precondition") FailedPrecondition
| @as("aborted") Aborted
| @as("out-of-range") OutOfRange
| @as("unimplemented") Unimplemented
| @as("internal") Internal
| @as("unavailable") Unavailable
| @as("data-loss") DataLoss
| @as("unauthenticated") Unauthenticated
// Added this @as(string) case, which will catch anything not specifically matched above
| @as(string) OtherCode
let code = ("cancelled" :> firestoreErrorCode) // Cancelled
let code = ("some random string" :> firestoreErrorCode) // OtherCode("some random string")
We’re missing one crucial piece for this to be really useful, which is narrowing a variant with a “default case” to one without. Tracking in this PR: Type spreads of regular variants in patterns by zth · Pull Request #6721 · rescript-lang/rescript-compiler · GitHub