Overview
Lutaml::Model implements a three-level architecture for XSD type declarations, aligned with W3C XML Schema 1.1 standards (§2.2.1, §2.2.2, §3.3, §3.4, §3.16).
This architecture ensures proper separation of concerns where:
-
Type definitions are intrinsic properties of Type classes
-
Type references are specified in element/attribute mappings
-
Type names for models are set at the model level
Three Levels of xsd_type
1. Type Class Level (Intrinsic Type Property)
Purpose: Define the XSD type that a custom Value type represents or derives from.
Location: [lib/lutaml/model/type/value.rb:107-110](lib/lutaml/model/type/value.rb)
XSD Standard: §3.16 Simple Type Definitions, §2.2.1 Type Definition Components
Implementation Status: ✅ IMPLEMENTED
class EmailType < Lutaml::Model::Type::String
xsd_type 'xs:normalizedString' (1)
def self.cast(value)
# Validation logic
value.to_s.strip
end
end| 1 | This custom type derives from xs:normalizedString in generated XSD |
Generated XSD:
<xs:simpleType name="EmailType">
<xs:restriction base="xs:normalizedString">
<!-- Additional constraints -->
</xs:restriction>
</xs:simpleType>Key Methods:
-
.xsd_type(type_name = nil)- Set or get XSD type for this Type class -
.default_xsd_type- Override to provide default XSD type (e.g.,"xs:string")
Built-in Types:
All built-in types have default_xsd_type implementations:
| Type Class | Default XSD Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| Dynamic (xs:IDREF or xs:string) |
2. Model Level (Type Definition Name)
Purpose: Define the XSD complexType or simpleType name for the model class.
Location: [lib/lutaml/model/xml/mapping.rb:172-176](lib/lutaml/model/xml/mapping.rb)
XSD Standard: §3.4 Complex Type Definitions
Implementation Status: ✅ IMPLEMENTED
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
xml do
xsd_type 'AddressType' (1)
map_element "street", to: :street
map_element "city", to: :city
end
end| 1 | Sets the complexType name in generated XSD |
Generated XSD:
<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="street" type="xs:string"/>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>Also Enables Type-Only Models:
When xsd_type is used without element, it creates a type-only model (no root element wrapper):
class AddressComponents < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
xml do
xsd_type # Type-only, no element wrapper
map_element "street", to: :street
map_element "city", to: :city
end
endThis sets @no_root = true and can be imported with import_model_mappings.
3. Element Level (Type Reference in Child Elements)
Purpose: Override the XSD type reference for a specific element in generated XSD.
Location: [lib/lutaml/model/xml/mapping_rule.rb](lib/lutaml/model/xml/mapping_rule.rb)
XSD Standard: §3.3 Element Declarations
Implementation Status: ❌ NOT YET IMPLEMENTED (To be added)
class Person < Lutaml::Model::Serializable
attribute :name, :string
attribute :email, EmailType
xml do
element 'person'
map_element "name", to: :name, xsd_type: 'xs:token' (1)
map_element "email", to: :email (2)
end
end| 1 | Explicit type reference override for this specific element |
| 2 | Type inferred from EmailType.xsd_type (falls back to Type level) |
Generated XSD:
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:token"/> <!-- Element-level override -->
<xs:element name="email" type="xs:normalizedString"/> <!-- From EmailType -->
</xs:sequence>
</xs:complexType>
</xs:element>Precedence Rules
For Element Type References
When generating <xs:element name="X" type="…"/>, the type is resolved in this order:
-
Element-level xsd_type (highest priority) - From mapping rule parameter
-
Type-level xsd_type - From Type::Value class method
-
default_xsd_type - Fallback from Type class
# Element-level > Type-level > Default
class Model < Lutaml::Model::Serializable
attribute :field, CustomType # CustomType.xsd_type = 'xs:token'
xml do
map_element "field", to: :field, xsd_type: 'xs:string' (1)
end
end| 1 | Element-level 'xs:string' wins over CustomType’s 'xs:token' |
Result: <xs:element name="field" type="xs:string"/>
For Model Type Definition
When generating <xs:complexType name="…">, the type name is:
-
Model-level xsd_type (explicit name) - From
xsd_type 'TypeName' -
Auto-generated from class name -
#{ClassName}Type
class Person < Lutaml::Model::Serializable
xml do
xsd_type 'PersonType' (1)
end
end| 1 | Explicit type name overrides auto-generated name |
Without explicit xsd_type:
class Person < Lutaml::Model::Serializable
xml do
element 'person'
end
endAuto-generates: PersonType
Current Implementation Status
| Level | Status | Location |
|---|---|---|
Type-level | ✅ Implemented |
|
Model-level | ✅ Implemented |
|
Element-level | ❌ Not Implemented | Need to add to |
Attribute-level (deprecated) | ✅ Warning Added |
|
Schema Generation | ⚠️ Partial |
|
Migration from Attribute-Level xsd_type
Status: Deprecation warning implemented in [lib/lutaml/model/attribute.rb:671-676](lib/lutaml/model/attribute.rb)
Old (Deprecated):
attribute :email, :string, xsd_type: 'xs:normalizedString' # ❌ DeprecatedWarning Message:
[DEPRECATION] The :xsd_type attribute option is deprecated and will be removed in v1.0.0.
Create a custom Type::Value class with xsd_type at class level instead.
See: docs/migration-guides/xsd-type-migration.adocMigration Path 1 - Custom Type (Recommended):
class EmailType < Lutaml::Model::Type::String
xsd_type 'xs:normalizedString'
end
class Person < Lutaml::Model::Serializable
attribute :email, EmailType
endMigration Path 2 - Element-level Mapping:
class Person < Lutaml::Model::Serializable
attribute :email, :string
xml do
map_element "email", to: :email, xsd_type: 'xs:normalizedString'
end
endMigration Path 3 - Use Built-in Type:
class Person < Lutaml::Model::Serializable
attribute :age, :integer # xs:integer is Type::Integer's default
endImplementation Roadmap
Phase 1: Add Element-Level xsd_type Support ⏳
Files to Modify:
-
lib/lutaml/model/xml/mapping_rule.rb-
Add
xsd_typeparameter toinitialize -
Add
attr_reader :xsd_type -
Pass through
deep_dup
-
-
lib/lutaml/model/xml/mapping.rb-
Add
xsd_type:parameter tomap_element -
Add
xsd_type:parameter tomap_attribute -
Pass to MappingRule constructor
-
-
lib/lutaml/model/schema/xsd_schema.rb-
Update
get_attribute_xsd_typeto check mapping rule first -
Update
build_element_attributesto use mapping rule xsd_type
-
New Precedence in Schema Generation:
def get_attribute_xsd_type(attr, attr_type, register, mapping_rule = nil)
# 1. Element-level (from mapping rule) - HIGHEST
return mapping_rule.xsd_type if mapping_rule&.xsd_type
# 2. Type-level (from Type class)
return attr_type.xsd_type if attr_type.respond_to?(:xsd_type)
# 3. Default fallback
get_xsd_type(attr_type)
endPhase 2: Comprehensive Testing ⏳
Test Coverage Needed:
-
Type-level xsd_type
-
Custom types with xsd_type
-
Inheritance of xsd_type
-
Fallback to default_xsd_type
-
-
Model-level xsd_type
-
Explicit type names
-
Auto-generated names
-
Type-only models (no_root)
-
-
Element-level xsd_type
-
Override Type-level xsd_type
-
Combine with transform
-
Use with collections
-
-
Precedence resolution
-
Element > Type > Default
-
Model > Auto-generated
-
-
Schema generation
-
All three levels separately
-
Combined usage
-
XSD 1.1 validation
-
-
Deprecation warnings
-
Attribute-level usage
-
Clear migration messages
-
Phase 3: Documentation Updates ⏳
Documents to Create/Update:
-
docs/_references/xsd-type-architecture.adoc- ✅ This document -
docs/migration-guides/xsd-type-migration.adoc- Detailed migration guide -
docs/_pages/value_types.adoc- Add xsd_type section -
README.adoc- Update examples -
XML-specific documentation
XSD Standard Compliance
Relevant W3C XML Schema 1.1 Sections
-
§2.2.1 - Type Definition Components
-
§2.2.2 - Declaration Components
-
§3.3 - Element Declarations
-
§3.4 - Complex Type Definitions
-
§3.16 - Simple Type Definitions
Architecture Alignment
| XSD Concept | Lutaml::Model | Location |
|---|---|---|
Simple Type Definition | Type::Value class | Type-level xsd_type |
Complex Type Definition | Model class | Model-level xsd_type |
Element Declaration | Element mapping | Element-level xsd_type |
Type Reference |
| Element/Attribute xsd_type |
Type Name |
| Model xsd_type value |
Examples
Complete Example: All Three Levels
# 1. Type-level: Custom ID type
class ProductIdType < Lutaml::Model::Type::String
xsd_type 'xs:ID' (1)
def self.cast(value)
value.to_s.upcase
end
end
# 2. Model-level: Address type definition
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
xml do
xsd_type 'AddressType' (2)
map_element "street", to: :street
map_element "city", to: :city
end
end
# 3. Element-level: Override for specific element
class Product < Lutaml::Model::Serializable
attribute :id, ProductIdType
attribute :name, :string
attribute :address, Address
xml do
element 'product'
map_element "id", to: :id # Uses ProductIdType.xsd_type = 'xs:ID'
map_element "name", to: :name, xsd_type: 'xs:token' (3)
map_element "address", to: :address
end
end| 1 | Type-level: ProductIdType intrinsically represents xs:ID |
| 2 | Model-level: Address generates <xs:complexType name="AddressType"> |
| 3 | Element-level: name element uses xs:token instead of default xs:string |
Generated XSD:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Type Definition from Type-level -->
<xs:simpleType name="ProductIdType">
<xs:restriction base="xs:ID"/>
</xs:simpleType>
<!-- Type Definition from Model-level -->
<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="street" type="xs:string"/>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!-- Element Declaration with Type References -->
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:ID"/> <!-- From Type-level -->
<xs:element name="name" type="xs:token"/> <!-- Element-level override -->
<xs:element name="address" type="AddressType"/> <!-- From Model-level -->
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>