The trick I’ve found is to define the component parts as single-element variants, then use variant spreads. It does mean that the shared Block module doesn’t really work, but it’s at least an option for a tradeoff.
module LevelId = {
type t = string
}
module PageBreak = {
@tag("block_type")
type t =
| @as("page-break-1.0") PageBreak({uuid: string, break_type: [#soft | #hard]})
}
module CustomSection = {
@tag("block_type")
type t =
| @as("custom-section-1.0") CustomSection({uuid: string, name: string, level_id: LevelId.t})
}
module CustomSectionTemplate = {
@tag("block_type")
type t =
| @as("custom-section-template-1.0") CustomSectionTemplate({uuid: string, name: string})
}
module ReportBlock = {
@tag("block_type")
type t =
| ...PageBreak.t
| ...CustomSection.t
}
module ReportTemplateBlock = {
@tag("block_type")
type t =
| ...PageBreak.t
| ...CustomSectionTemplate.t
}
module Examples = {
let x: ReportBlock.t = PageBreak({uuid: "1", break_type: #soft})
let y: ReportBlock.t = CustomSection({
uuid: "2",
name: "Some section",
level_id: "0000-0000",
})
let x1: ReportTemplateBlock.t = PageBreak({uuid: "3", break_type: #soft})
let y1: ReportTemplateBlock.t = CustomSectionTemplate({
uuid: "4",
name: "Some section",
})
}
I used this pretty liberally in my graphql bindings for codegen.