Here’s a couple smaller comments that don’t pertain to the overall design.
1
It’s possible to overuse parameters for custom types. If you just want a field to be a String, then make it a String, rather than a T <: AbstractString. This is where you can rely on the implicit convert machinery that already exists. For example, if you try to assign a SubString to a String field, Julia will automatically convert the SubString to a String:
julia> struct ID
id::String
end
julia> ID(SubString("abcd", 2:3))
ID("bc")
2
The XsdRestriction type has non-concrete field types, which can lead to poor performance. (Maybe you just omitted some of the type details to simplify the example.)
struct XsdRestriction
base::QName
id::Union{ID,Nothing}
value::XsdNonNegativeInt
end
Making QName, ID, and XsdNonNegativeInt non-parametric (in line with my first comment), would solve this. Something like
struct QName <: XsdAnySimpleType
pfx::Union{String,Nothing}
nm::String
end
struct XsdNonNegativeInt <: XsdAnySimpleType
v::UInt
end
struct ID
id::String
end