Skip to main content
Version: Next 🚧

Xml Basic Support Proposal

To get basic support for xml we should get parity with OpenAPI

This means we need to have ways of specifying the following:

Field NameTypeDescription
namestringReplaces the name of the element/attribute used for the described schema property. When defined within items, it will affect the name of the individual XML elements within the list. When defined alongside type being array (outside the items), it will affect the wrapping element and only if wrapped is true. If wrapped is false, it will be ignored.
namespacestringThe URI of the namespace definition. This MUST be in the form of an absolute URI.
prefixstringThe prefix to be used for the name.
attributebooleanDeclares whether the property definition translates to an attribute instead of an element. Default value is false.
wrappedbooleanMAY be used only for an array definition. Signifies whether the array is wrapped (for example, ) or unwrapped (). Default value is false. The definition takes effect only when defined alongside type being array (outside the items).
x-ms-textbooleanAutorest doc | OpenAPI Issue

Attributes coverage

1. name

Name is something we can already provide with @encodedName("application/xml", "newname")

1.b Add @Xml.name decorator

Add an @Xml.name decorator that would just call @encodedName("application/xml", "newname") for you. It should not be saving any extra state. It should really just be a shorthand for @encodedName("application/xml", "newname") and in no way using @xml.name("newname") achieve different functionality from @encodedName("application/xml", "newname").

2. attribute

Decorator would specify that this property should be serialized as an attribute instead.

extern dec attribute(target: ModelProperty);

Restrictions:

  • @attribute can only be applied to model properties of scalar types

3. wrapped and x-ms-text

Proposing that when dealing with arrays we always wrap them by default. We are saying that there is always a node created for a property. We then have a decorator @unwrapped that allows you to not wrap the property content. In the case of an array property this is equivalent to setting wrapped: false in OpenAPI. In the case of a scalar property this is equivalent to setting x-ms-text: true in OpenAPI with Autorest extensions.

extern dec unwrapped(target: ModelProperty);

4. namespace and prefix

Define a new namespace decorator that can be used in two ways.

extern dec ns(target: unknown, prefix: string, namespace: string)
extern dec ns(target: unknown, namespace: EnumMember)
  1. Simple but more verbose as you need to keep reusing the same namespace
@ns("ns1", "https://example.com/ns1")
model Foo {
@ns("ns1", "https://example.com/ns1")
bar: string
@ns("ns2", "https://example.com/ns2")
bar: string
}

You could also use an alias to reuse

alias ns1 = "https://example.com/ns1";
alias ns2 = "https://example.com/ns2";
@ns("ns1", ns1)
model Foo {
@ns("ns1", ns1)
bar: string
@ns("ns2", ns2)
bar: string
}
  1. Using an enum to define the namespace:
enum Namespaces {
ns1 = "https://example.com/ns1",
ns2 = "https://example.com/ns2"
}

@Xml.ns(Namespaces.ns1)
model Foo {
@Xml.ns(Namespaces.ns1)
bar: string
@Xml.ns(Namespaces.ns2)
bar: string
}

4.a Do we need a decorator to annoate the enum?

@Xml.nsDeclarations
enum Namespaces {
ns1 = "https://example.com/ns1",
ns2 = "https://example.com/ns2"
}

@Xml.ns(Namespaces.ns1)
model Foo {
@Xml.ns(Namespaces.ns1)
bar: string
@Xml.ns(Namespaces.ns2)
bar: string
}

Default encoding of scalars

As in Json we need to have some default handling of the common scalars like utcDateTime

Scalar TypeDefault EncodingEncoding name
utcDateTimexs:dateTimeTypeSpec.Xml.Encoding.xmlDateTime
offsetDateTimexs:dateTimeTypeSpec.Xml.Encoding.xmlDateTime
plainDatexs:dateTypeSpec.Xml.Encoding.xmlDate
plainTimexs:timeTypeSpec.Xml.Encoding.xmlTim
durationxs:durationTypeSpec.Xml.Encoding.xmlDuration
bytesxs:base64BinaryTypeSpec.Xml.Encoding.xmlBase64Binary

Changes to add

Create new @typespec/xml library

New decorators

namespace TypeSpec.Xml
extern dec name(target: ModelProperty);
extern dec attribute(target: ModelProperty);
extern dec unwrapped(target: ModelProperty);
extern dec ns(target: unknown, namespaceOrPrefix: EnumMember | valueof string, namespace?: string);
extern dec nsDeclaration(target: Enum);

enum Encoding {
/** Maps to encoding to a value used in xs:date */
xmlDate,
/** Maps to encoding to a value used in xs:time */
xmlTime,
/** Maps to encoding to a value used in xs:dateTime */
xmlDateTime,
/** Maps to encoding to a value used in xs:duration */
xmlDuration,
/** Maps to encoding to a value used in xs:base64Binary */
xmlBase64Binary,
}

Apis to Add

To figure out if it is better to have those as fully qualifed id to the enum member name or just return the enum member directly. This most likely need a change in the compiler to

  1. return the fully qualified enum member name(unless they are from the compiler)
  2. return the enum member directly via a different api ?
export function resolveXmlEncoding(type: Scalar): XmlEncoding | string;

export type XmlEncoding =
| "TypeSpec.Xml.Encoding.xmlDate"
| "TypeSpec.Xml.Encoding.xmlDate"
| "TypeSpec.Xml.Encoding.xmlDateTime"
| "TypeSpec.Xml.Encoding.xmlDuration"
| "TypeSpec.Xml.Encoding.xmlBase64Binary"
| string;

Examples

1. Array of primitive types

Scenario TypeSpec Xml OpenAPI3

Scenario 1.1:

  • ❌ ItemsName
  • ❌ Wrapped
@encodedName("application/xml", "XmlPet")
model Pet {
@xml.unwrapped
tags: string[];
}
<XmlPet>
<tags>abc</tags>
<tags>def</tags>
</XmlPet>
Pet:
type: "object"
properties:
tags:
type: "array"
items:
type: string
xml:
name: "XmlPet"

Scenario 1.2:

  • ❌ ItemsName
  • ✅ Wrapped
@encodedName("application/xml", "XmlPet")
model Pet {
@encodedName("application/xml", "ItemsTags")
tags: string[];
}
<XmlPet>
<ItemsTags>
<string>abc</string>
<string>def</string>
</ItemsTags>
</XmlPet>
Pet:
type: "object"
properties:
tags:
type: "array"
xml:
name: "ItemsTags"
wrapped: true
items:
type: string
xml:
name: "XmlPet"

Scenario 1.3:

  • ✅ ItemsName
  • ❌ Wrapped
@encodedName("application/xml", "ItemsName")
scalar tag extends string;

@encodedName("application/xml", "XmlPet")
model Pet {
@xml.unwrapped
tags: tag[];
}
<XmlPet>
<ItemsName>abc</ItemsName>
<ItemsName>def</ItemsName>
</XmlPet>
Pet:
type: "object"
properties:
tags:
type: "array"
xml:
name: "ItemsTags"
items:
type: string
xml:
name: ItemsName
xml:
name: "XmlPet"

Scenario 1.4:

  • ✅ ItemsName
  • ✅ Wrapped
@encodedName("application/xml", "ItemsName")
scalar tag extends string;

@encodedName("application/xml", "XmlPet")
model Pet {
@encodedName("application/xml", "ItemsTags")
tags: tag[];
}
<XmlPet>
<ItemsTags>
<ItemsName>abc</ItemsName>
<ItemsName>def</ItemsName>
</ItemsTags>
</XmlPet>
Pet:
type: "object"
properties:
tags:
type: "array"
xml:
name: "ItemsTags"
wrapped: true
items:
type: string
xml:
name: ItemsName
xml:
name: "XmlPet"

2. Complex array types

Scenario TypeSpec Xml OpenAPI3

Scenario 2.1:

  • ❌ ItemsName
  • ❌ Wrapped
@encodedName("application/xml", "XmlPet")
model Pet {
@xml.unwrapped
tags: Tag[];
}

@encodedName("application/xml", "XmlTag")
model Tag {
name: string;
}
<XmlPet>
<XmlTag>
<name>string</name>
</XmlTag>
</XmlPet>
Tag:
type: "object"
properties:
name:
type: "string"
xml:
name: "XmlTag"
Pet:
type: "object"
properties:
tags:
type: "array"
items:
$ref: "#/definitions/Tag"
xml:
name: "XmlPet"

Scenario 2.2:

  • ❌ ItemsName
  • ✅ Wrapped
@encodedName("application/xml", "XmlPet")
model Pet {
tags: Tag[];
}

@encodedName("application/xml", "XmlTag")
model Tag {
name: string;
}
<XmlPet>
<ItemsTags>
<XmlTag>
<name>string</name>
</XmlTag>
</ItemsTags>
</XmlPet>
Tag:
type: "object"
properties:
name:
type: "string"
xml:
name: "XmlTag"
Pet:
type: "object"
properties:
tags:
type: "array"
xml:
name: "ItemsTags"
wrapped: true
items:
$ref: "#/definitions/Tag"
xml:
name: "XmlPet"

Scenario 2.3:

  • ✅ ItemsName
  • ❌ Wrapped
@encodedName("application/xml", "XmlPet")
model Pet {
@encodedName("application/xml", "ItemsTags")
@xml.unwrapped
tags: Tag[];
}

@encodedName("application/xml", "XmlTag")
model Tag {
name: string;
}
<XmlPet>
<ItemsTag>
<name>string</name>
</ItemsTag>
</XmlPet>
Tag:
type: "object"
properties:
name:
type: "string"
xml:
name: "XmlTag"
Pet:
type: "object"
properties:
tags:
type: "array"
xml:
name: "ItemsTags"
items:
$ref: "#/definitions/Tag"
xml:
name: ItemsXMLName
xml:
name: "XmlPet"

Scenario 2.4:

  • ✅ ItemsName
  • ✅ Wrapped
@encodedName("application/xml", "XmlPet")
model Pet {
@encodedName("application/xml", "ItemsTags")
tags: Tag[];
}

@encodedName("application/xml", "XmlTag")
model Tag {
name: string;
}
<XmlPet>
<ItemsTags>
<XmlTag>
<name>string</name>
</XmlTag>
</ItemsTags>
</XmlPet>
Tag:
type: "object"
properties:
name:
type: "string"
Pet:
type: "object"
properties:
tags:
type: "array"
xml:
name: "ItemsTags"
wrapped: true
items:
$ref: "#/definitions/Tag"
xml:
name: "XmlPet"

3. Nested models

Scenario TypeSpec Xml OpenAPI3

Scenario 3.1:

No annotations

model Book {
author: Author;
}

model Author {
name: string;
}
<Book>
<author>
<name>string</name>
</author>
</Book>
Book:
type: object
properties:
author:
$ref: "#/components/schemas/Author"
Author:
type: object
properties:
name:
type: string

Scenario 3.2:

Nested model has xml encoded name.

⚠️ no op in serialization of Book

model Book {
author: Author;
}

@encodedName("application/xml", "XmlAuthor")
model Author {
name: string;
}
<Book>
<author>
<name>string</name>
</author>
</Book>
Book:
type: object
properties:
author:
allOf:
- $ref: "#/components/schemas/Author"
xml:
name: "author" # Here we have to redefine this name otherwise in OpenAPI semantic the `XmlAuthor` name would be used
Author:
type: object
properties:
name:
type: string
xml:
name: "XmlAuthor"

Scenario 3.2:

Property has encoded name

model Book {
@encodedName("application/xml", "xml-author")
author: Author;
}

model Author {
name: string;
}
<Book>
<xml-author>
<name>string</name>
</xml-author>
</Book>
Book:
type: object
properties:
author:
allOf:
- $ref: "#/components/schemas/Author"
xml:
name: "xml-author"
Author:
type: object
properties:
name:
type: string

4. Attributes

Scenario TypeSpec Xml OpenAPI3

Scenario 4.1:

Convert a property to an attribute

model Book {
@Xml.attribute
id: string;

title: string;
author: string;
}
<Book id="0">
<xml-title>string</xml-title>
<author>string</author>
</Book>
Book:
type: object
properties:
id:
type: integer
title:
type: string
xml:
name: "xml-title"
author:
type: string

5. Namespace and prefix (inline form)

Scenario TypeSpec Xml OpenAPI3

Scenario 5.1:

On model

@Xml.ns("smp", "http://example.com/schema")
model Book {
id: string;
title: string;
author: string;
}
<smp:Book xmlns:smp="http://example.com/schema">
<id>0</id>
<title>string</title>
<author>string</author>
</smp:Book>
Book:
type: object
properties:
id:
type: integer
title:
type: string
author:
type: string
xml:
prefix: "smp"
namespace: "http://example.com/schema"

Scenario 5.2:

On model and properties

@Xml.ns("smp", "http://example.com/schema")
model Book {
id: string;
@Xml.ns("smp", "http://example.com/schema")
title: string;
@Xml.ns("ns2", "http://example.com/ns2")
author: string;
}
<smp:Book xmlns:smp="http://example.com/schema" xmlns:sn2="http://example.com/ns2">
<id>0</id>
<smp:title>string</smp:title>
<ns2:author>string</ns2:author>
</smp:Book>
Book:
type: object
properties:
id:
type: integer
title:
type: string
xml:
prefix: "smp"
namespace: "http://example.com/schema"
author:
type: string
xml:
prefix: "ns2"
namespace: "http://example.com/ns2"
xml:
prefix: "smp"
namespace: "http://example.com/schema"

6. Namespace and prefix (normalized form)

Scenario TypeSpec Xml OpenAPI3

Scenario 6.1:

On model

@Xml.nsDeclarations
enum Namespaces {
smp = "http://example.com/schema"
}

@Xml.ns(Namespaces.smp)
model Book {
id: string;
title: string;
author: string;
}
<smp:Book xmlns:smp="http://example.com/schema">
<id>0</id>
<title>string</title>
<author>string</author>
</smp:Book>
Book:
type: object
properties:
id:
type: integer
title:
type: string
author:
type: string
xml:
prefix: "smp"
namespace: "http://example.com/schema"

Scenario 6.2:

On model and properties

@Xml.nsDeclarations
enum Namespaces {
smp = "http://example.com/schema",
ns2 = "http://example.com/ns2"
}

@Xml.ns(Namespaces.smp)
model Book {
id: string;
@Xml.ns(Namespaces.smp)
title: string;
@Xml.ns(Namespaces.ns2)
author: string;
}
<smp:Book xmlns:smp="http://example.com/schema" xmlns:sn2="http://example.com/ns2">
<id>0</id>
<smp:title>string</smp:title>
<ns2:author>string</ns2:author>
</smp:Book>
Book:
type: object
properties:
id:
type: integer
title:
type: string
xml:
prefix: "smp"
namespace: "http://example.com/schema"
author:
type: string
xml:
prefix: "ns2"
namespace: "http://example.com/ns2"
xml:
prefix: "smp"
namespace: "http://example.com/schema"

6. Property setting the text of the node

Scenario TypeSpec Xml OpenAPI3

Scenario 6.1:

model BlobName {
@Xml.attribute language: string;
@Xml.unwrapped content: string;
}
<BlobName language="abc">
...content...
</smp:Book>
Book:
type: object
properties:
language:
type: string
content:
type: string
xml:
x-ms-text: true # on autorest emitter