Skip to content

Visibility

Visibility is a language feature that allows you to share a model between multiple operations and define in which contexts properties of the model are “visible.” Visibility is a very powerful feature that allows you to define different “views” of a model within different operations or contexts.

Basic concepts

  • Visibility applies to model properties only. It is used to determine when an emitter should include or exclude a property in a certain context.
  • Visibility is defined using a visibility class. A visibility class is an enum that defines the visibility modifiers (or flags) that can be applied to a property. Any enum can serve as a visibility class.
  • Visibility classes have a default visibility, which is the set of visibility modifiers that are applied by default to a property if the visibility is not explicitly set.

Lifecycle visibility

TypeSpec provides a built-in visibility called “resource lifecycle visibility.” This visibility allows you to declare whether properties are visible when a creating, updating, or reading a resource from an API endpoint. For example:

model Example {
/**
* The unique identifier of this resource.
*
* The ID is automatically generated by the service, so it cannot be set when the resource is created or updated,
* but the server will return it when the resource is read.
*/
@visibility(Lifecycle.Read)
id: string;
/**
* The name of this resource.
*
* The name can be set when the resource is created, but may not be changed.
*/
@visibility(Lifecycle.Create, Lifecycle.Read)
name: string;
/**
* The description of this resource.
*
* By default, properties are visible in all three lifecycle phases, so this
* property can be set when the resource is created, updated, and read.
*/
description: string;
}

In the above example, each property of the Example model has a lifecycle visibility that instructs emitters to include or exclude the property when creating, updating, or reading the Example resource.

TypeSpec’s HTTP library, OpenAPI emitter, and other standard functionality use the Lifecycle visibility to create different views of the Example model based on which lifecycle phase is used in a particular operation.

In the following example, the type of the input and output of each operation is affected by the lifecycle visibility of the properties in the Example model.

@route("/example")
interface Examples {
/**
* When an operation uses the POST verb, it uses the `Create` lifecycle visibility to determine which properties
* are visible.
*/
@post create(@body example: Example): Created<Example> | Error;
/**
* When an operation uses the GET verb, it uses the `Read` lifecycle visibility to determine which properties
* are visible.
*/
@get read(@path id: string): Ok<Example> | Error;
/**
* When an operation uses the PATCH verb, it uses the `Update` lifecycle visibility to determine which properties
* are visible.
*/
@patch update(@path id: string, @body example: Example): Ok<Example> | Error;
}

The above interface generates the following OpenAPIv3 schemas:

paths:
/example:
post:
parameters: []
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Example"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Example"
/example/{id}:
get:
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Example"
patch:
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Example"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ExampleUpdate"
components:
schemas:
Example:
type: object
required:
- id
- name
- description
properties:
id:
type: string
readOnly: true
name:
type: string
description:
type: string
ExampleUpdate:
type: object
properties:
description:
type: string

Notice:

  • The id property is marked readOnly: true because it is only visible when reading the resource.
  • The ExampleUpdate schema only includes the description property because it is the only property that is visible when updating the resource.
  • Each of the paths reference the correct schema based on the lifecycle phase that the operations use.
  • The TypeSpec model is only defined once, and any changes in the output schemas are derived from the lifecycle visibility of the properties in the model.

Lifecycle visibility transforms

You can explicitly compute the shape of a model within a specific lifecycle phase by using the four built-in templates for lifecycle transforms:

  • Create<T extends Model>: creates a copy of T with only the properties that are visible in the Create lifecycle phase, recursively.
  • Read<T extends Model>: creates a copy of T with only the properties that are visible in the Read lifecycle phase, recursively.
  • Update<T extends Model>: creates a copy of T with only the properties that are visible in the Update lifecycle phase, with the types of the properties set to CreateOrUpdate<T>, recursively.
  • CreateOrUpdate<T>: creates a copy of T with only the properties that have either the Create or Update visibility modifiers enabled, recursively.

For example:

model Example {
@visibility(Lifecycle.Create)
id: string;
@visibility(Lifecycle.Create, Lifecycle.Read)
name: string;
@visibility(Lifecycle.Update)
description: string;
}
model ReadExample is Read<Example>;
model CreateExample is Create<Example>;
model UpdateExample is Update<Example>;
model CreateOrUpdateExample is CreateOrUpdate<Example>;

When you use these templates, the resulting models have no Lifecycle visibility modifiers applied, so that any emitters or libraries that use lifecycle visibility will not alter them further.

Visibility modifiers

Each property has its own set of active visibility modifiers for each visibility class. The active modifiers can be changed using the decorators described in this section.

Note: Changing the visibility for one visibility class does not affect other visibility classes. If you change the visibility for the Lifecycle visibility class, it will not affect the modifiers that are active for any other visibility classes.

@visibility

The @visibility decorator enables visibility modifiers. It takes a list of visibility modifiers as arguments and sets them on the property. For example:

@visibility(Lifecycle.Create, Lifecycle.Read)
name: string;

In this example, the name property has the Create and Read visibility modifiers enabled.

If visibility has already been set explicitly on a property, the @visibility decorator ADDS its own visibility modifiers to the currently-active modifiers. It does not replace the existing modifiers. For example:

@visibility(Lifecycle.Create)
@visibility(Lifecycle.Read)
name: string;

In this example, the name property has both the Create and Read visibility modifiers enabled, but not the Update visibility modifier. The @visibility decorator starts from an empty set of modifiers and adds the Create modifier, then adds the Read modifier.

@removeVisibility

The @removeVisibility decorator disables visibility modifiers. It takes a list of visibility modifiers as arguments and removes them from the property. For example:

@removeVisibility(Lifecycle.Update)
name: string;

This use of @removeVisibility is equivalent to the above examples with the @visibility decorator, but it uses the @removeVisibility decorator to remove the Update visibility modifier from the name property rather than adding the Create and Read visibility modifiers. The @removeVisibility decorator starts from the default set of visibility modifiers and removes the Update modifier.

If the visibility has already been set on a property, the @removeVisibility decorator removes its visibility from the currently-active modifiers. It does not replace the existing modifiers. For example:

@removeVisibility(Lifecycle.Update)
@removeVisibility(Lifecycle.Create)
id: string;

In this example, the id property has the Update and Create visibility modifiers removed, but it retains the Read visibility modifier.

@invisible

The @invisible decorator disables all visibility modifiers on a property within a given visibility class. For example:

@invisible(Lifecycle)
invisible: string;

In this example, the invisible property has no visibility modifiers enabled in the Lifecycle visibility class.

Visibility filters

The @withVisibilityFilter decorator allows you to transform a model by applying a visibility filter to it. A visibility filter is an object that defines constraints on which visibility modifiers must be enabled/disabled for a property to be visible. For example:

model Example {
@visibility(Lifecycle.Create)
id: string;
@visibility(Lifecycle.Create, Lifecycle.Read)
name: string;
@visibility(Lifecycle.Update)
description: string;
}
@withVisibilityFilter(#{ all: [Lifecycle.Create, Lifecycle.Read] })
model CreateAndReadExample {
...Example;
}
@withVisibilityFilter(#{ any: [Lifecycle.Create, Lifecycle.Update] })
model CreateOrUpdateExample {
...Example;
}
@withVisibilityFilter(#{ none: [Lifecycle.Update] })
model NonUpdateExample {
...Example;
}

In the above example, the CreateAndReadExample model is a copy of the Example model with only the the properties that have BOTH the Create and Read visibility modifiers enabled (i.e. only the name property). The CreateOrUpdateExample model is a copy of the Example model with only the properties that have EITHER the Create or Update visibility modifiers enabled (i.e. the id and name properties). The NonUpdateExample model is a copy of the Example model with only the properties that do not have the Update visibility modifier enabled (i.e. the id and name properties).

Note: For Lifecycle visibility, you should ordinarily use the Create, Read, Update, and CreateOrUpdate templates instead of @withVisibilityFilter directly, but you can use @withVisibilityFilter to create custom “views” of a model that use visibility classes other than Lifecycle or custom filter logic.

Visibility classes

Any TypeSpec enum can serve as a visibility class. The members of the enum define the visibility modifiers in the class. For example, the following is the definition of the Lifecycle visibility class defined in the TypeSpec standard library:

enum Lifecycle {
Create,
Read,
Update,
}

This visibility class defines three visibility modifiers: Create, Read, and Update. By default, all properties have ALL three visibilities in the Lifecycle enum enabled.

Setting default visibility

You can set the default visibility for a visibility class by declaring it on the enum using the @defaultVisibility decorator:

@defaultVisibility(Example.A)
enum Example {
A,
B,
}

In this example, any property that does not declare an Example visibility modifier will have the A visibility by default.

Note: While you can define your own visibility classes, emitters will not recognize them unless they have been programmed to do so. You can leverage custom visibility classes in your own emitters, but they will have no effect on the standard emitters unless those emitters choose to adopt and recognize those visibility classes as meaningful. The Lifecycle visibility class is a standard visibility class that is recognized by several emitters. You can, however, use your own visibility classes with the built in @withVisibilityFilter decorator to transform your models in whatever ways you see fit.