Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions graph/GuidelinesGraph.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,13 @@ The three most often used patterns in Microsoft Graph today are type hierarchy,

- **[Enums](./patterns/enums.md)** represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options.

The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case.
The following table shows a summary of the main qualities for each pattern and can help you select a pattern fit for your use case. The **TypeSpec representation** column shows how each pattern is authored with the [`@microsoft/typespec-msgraph`](https://aka.ms/typespec) library; see the [facets TypeSpec example](./patterns/facets.md#facets-in-typespec) for a worked sample.

| API qualities\patterns | Properties and behavior described in metadata | Supports combinations of properties and behaviors | Simple query construction |
|-------------------------|-----------------------------------------------|---------------------------------------------------|---------------------------|
| Type hierarchy | yes | no | no |
| Facets | partially | yes | yes |
| Flat bag | no | no | yes |
| API qualities\patterns | Properties and behavior described in metadata | Supports combinations of properties and behaviors | Simple query construction | TypeSpec representation |
|-------------------------|-----------------------------------------------|---------------------------------------------------|---------------------------|-------------------------|
| Type hierarchy | yes | no | no | `@abstract` + `extends` |
| Facets | partially | yes | yes | `@facet` on a nullable `@complex` property |
| Flat bag | no | no | yes | `@flatBag` + `@variant` |

#### Pros and cons

Expand Down
95 changes: 95 additions & 0 deletions graph/patterns/facets.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,98 @@ Response shortened for readability:
}
]
```

## Facets in TypeSpec

In [TypeSpec](https://aka.ms/typespec), the facets pattern is expressed with the `@facet` decorator from the `@microsoft/typespec-msgraph` library. A facet is a **nullable property** on an `@entity` model whose type is a `@complex` model. The `@facet` decorator marks the property as a variant facet so that authoring tooling and linters can validate it.

The following TypeSpec models the same `driveItem` facets shown in the CSDL example above — each variant (`audio`, `file`, `folder`, `image`, `video`) is a complex type, and the entity carries one nullable facet property per variant:

```typespec
// One @complex type per variant facet
@complex
model audio {
@doc("The title of the album for this audio file.")
album: string | null;
}

@complex
model file {
@doc("The MIME type for the file.")
mimeType: string | null;
}

@complex
model folder {
@doc("Number of children contained immediately within this container.")
childCount: int32 | null;
}

@complex
model image {
@doc("Width of the image, in pixels.")
width: int32 | null;

@doc("Height of the image, in pixels.")
height: int32 | null;
}

@complex
model video {
@doc("Duration of the video, in milliseconds.")
duration: int64 | null;
}

// The entity declares one nullable facet property per variant
@entity
model driveItem {
@key id: string;

@doc("The name of the item (file name, folder name).")
displayName: string | null;

@doc("Audio facet. Present when the item is an audio file.")
@facet audio: audio | null;

@doc("File facet. Present when the item is a file.")
@facet file: file | null;

@doc("Folder facet. Present when the item is a folder.")
@facet folder: folder | null;

@doc("Image facet. Present when the item is an image.")
@facet image: image | null;

@doc("Video facet. Present when the item is a video.")
@facet video: video | null;
}
```

### Rules for `@facet`

The `@facet` decorator is validated by the `@microsoft/typespec-msgraph` linter:

- The property type must be a `@complex` model — primitive, enum, and entity types are rejected.
- The property must be **nullable** (`T | null`) — a variant may be absent.
- The property must be declared on an `@entity` model — facets on `@complex` models are rejected.

### Compiled CSDL

`@facet` is an **authoring-time marker**: it carries no wire-format meaning, so a facet property compiles to a standard nullable complex-typed property — identical to what a hand-authored facet produces. The TypeSpec above emits:

```xml
<EntityType Name="driveItem">
<Key>
<PropertyRef Name="id"/>
</Key>
<Property Name="id" Type="Edm.String" Nullable="false"/>
<Property Name="displayName" Type="Edm.String" Nullable="true"/>
<Property Name="audio" Type="graph.audio" Nullable="true"/>
<Property Name="file" Type="graph.file" Nullable="true"/>
<Property Name="folder" Type="graph.folder" Nullable="true"/>
<Property Name="image" Type="graph.image" Nullable="true"/>
<Property Name="video" Type="graph.video" Nullable="true"/>
</EntityType>
```

Because `@facet` adds no CSDL annotation, applying or omitting it produces byte-identical metadata; its value is the design intent it records and the linter checks it enables.
50 changes: 49 additions & 1 deletion graph/patterns/flat-bag.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,52 @@ The recurrencePattern has six variants expressed as six different values of the
<Property Name="month" Type="Edm.Int32" Nullable="false" />
<Property Name="type" Type="graph.recurrencePatternType" />
</ComplexType>
```
```

### TypeSpec representation

The same `recurrencePattern` flat-bag is expressed in TypeSpec using the `@flatBag` decorator on the model plus `@variant` decorators on the variant-conditional sibling properties.
The discriminator property is conventionally named `type`, and the discriminator enum must be closed (no `unknownFutureValue`).

```TypeSpec
enum recurrencePatternType {
daily: 0,
weekly: 1,
absoluteMonthly: 2,
relativeMonthly: 3,
absoluteYearly: 4,
relativeYearly: 5,
}

@flatBag("type")
@complex model recurrencePattern {
type: recurrencePatternType | null;

// Shared property — meaningful for every variant
interval: int32;

// wire-fidelity: shipped as Nullable="false"; variant-conditional but non-nullable
@variant("absoluteMonthly", "absoluteYearly") dayOfMonth: int32;

@variant("weekly", "relativeMonthly", "relativeYearly") daysOfWeek: dayOfWeek[];

@variant("weekly") firstDayOfWeek: dayOfWeek | null;

@variant("relativeMonthly", "relativeYearly") index: weekIndex | null;

// wire-fidelity: shipped as Nullable="false"; variant-conditional but non-nullable
@variant("absoluteYearly", "relativeYearly") month: int32;
}
```

The `@flatBag` and `@variant` decorators are pure semantic metadata — they do not change the compiled CSDL output, which matches the CSDL shown above.
Their purpose is to enable validation that variant-conditional sibling properties target valid discriminator enum members, and to surface the pattern to downstream tooling.

**Authoring rules:**

- The discriminator property is typed as `<EnumName> | null` to match the shipped Graph CSDL convention.
- Variant-conditional non-collection siblings are nullable (`T | null`).
- Variant-conditional collection siblings use `T[]` rather than `T[] | null` — empty array signals absence per Graph wire convention.
- Properties meaningful for every variant (`interval` above) remain unannotated.
- Non-nullable variant-conditional siblings (like `dayOfMonth`, `month`) preserve the shipped Graph wire shape.
The inline `// wire-fidelity:` comment documents the exception, and a `#suppress` for `flat-bag-siblings-should-be-nullable` may be needed under stricter rulesets.
41 changes: 41 additions & 0 deletions graph/patterns/subtypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,47 @@ Groups and users are derived types and modeled as follows:
</EntityType>
```

### TypeSpec representation

The same `directoryObject` hierarchy is expressed in TypeSpec using the `@abstract` decorator on the base type and the native `extends` keyword on each derived type.
No purpose-built decorators are needed beyond `@abstract`, `@entity`, and `@key`.

```TypeSpec
@abstract
@entity model entity {
@key id: string;
}

@abstract
@entity model directoryObject extends entity {
deletedDateTime: utcDateTime | null;
}

@entity model group extends directoryObject {
description: string | null;
// ... other group-specific properties
}

@entity model user extends directoryObject {
jobTitle: string | null;
// ... other user-specific properties
}
```

The `@abstract` decorator emits `Abstract="true"` in CSDL and marks the type as non-instantiable — consumers must POST or PATCH against a derived type.
Each derived type inherits the base properties (including the key) via `extends` and adds only its own variant-specific properties.
The TypeSpec compiler rejects redeclaration of base properties on derived types.

**Authoring rules:**

- The base type carries `@abstract` and the `@key` property; derived types inherit both via `extends`.
- Derived types use `extends <BaseName>` and add only their own variant-specific properties.
- Derived types do NOT redeclare base properties (compile error).
- Hierarchy depth ≤ 3 levels — deeper hierarchies usually indicate wrong modeling.

The compiled CSDL matches the structure shown above.
URL and query semantics shown in the following sections apply regardless of authoring language.

An API request to get members of a group returns a heterogeneous collection of
users and groups where each element can be a user or a group, and has an
additional `@odata.type` property that specifies the subtype:
Expand Down