This package has been deprecated

Author message:

@azure-tools/cadl- packages are deprecated use @azure-tools/typespec- instead.

@azure-tools/cadl-azure-core
TypeScript icon, indicating that this package has built-in type declarations

0.26.0 • Public • Published

Cadl Azure Core Library

This package provides Cadl decorators, models and interfaces to describe Azure services. With models, decorators and interfaces defined in Azure.Core, you will be able to define Azure resources and operations using standard patterns and best practices with ease.

Table of Contents

Getting Started

To get started using Cadl and Azure.Core, you have two options:

  1. Visit the Cadl Azure Playground to experiment with Cadl in your web browser (recommended for first-time users)
  2. Follow the installation documentation at the official documentation website (recommended for real Cadl development)

You should also read the language overview to get better acquainted with the language.

Creating a new Cadl project with cadl init

If you installed Cadl on your local machine, here is how you can create a new Cadl project:

First, open your command prompt (PowerShell, cmd.exe, bash, etc), create an empty folder for your new project, and cd into it.

Now create a new Azure service specification using the cadl init command:

cadl init https://aka.ms/cadl/core-init

You will be prompted with a few questions:

  • The service template: choose "Azure Data Plane Service"
  • The project name: Enter a name to be used as the project folder name or press enter to use the same name as the folder you created
  • Update the libraries: Press Enter to continue with the selected packages

The prompts will look something like this:

Cadl compiler v0.34.0

√ Please select a template » Azure Data Plane Service
√ Project name ... myService
√ Update the libraries? » @cadl-lang/rest, @cadl-lang/versioning, @azure-tools/cadl-autorest, @azure-tools/cadl-azure-core
Cadl init completed. You can run `cadl install` now to install dependencies.

Once your project files have been created, execute the following command to install the Cadl compiler and libraries:

cadl install

You can now open the file main.cadl to follow along with the rest of the tutorial!

Writing Your First Service

The Azure Data Plane Service template will create a very basic Cadl file in main.cadl:

import "@cadl-lang/rest";
import "@cadl-lang/versioning";
import "@azure-tools/cadl-autorest";
import "@azure-tools/cadl-azure-core";

These lines import the libraries you will need to build your first service.

NOTE: The @azure-tools/cadl-autorest import is not explicitly needed for this tutorial.

Add the following lines to bring the models, operations, and decorators you will need into the specification:

using Cadl.Http;
using Cadl.Rest;
using Cadl.Versioning;
using Azure.Core;

Create the service namespace

To describe a service, you first need to define a "blockless" (file-level, no curly braces) namespace and use the @service decorator to mark it as the service namespace:

@service({
  title: "Contoso Widget Manager",
})
namespace Contoso.WidgetManager;

This marks the Contoso.WidgetManager namespace as a service namespace in this Cadl specification and sets its title to "Contoso Widget Manager."

Using the versioned Azure.Core types

Before you can use the models and operations defined in the Azure.Core namespace, you will need to specify the API version of the Azure.Core library that your service uses. You can do this by adding the @versionedDependency decorator to the Contoso.WidgetManager namespace as seen here:

@service({
  title: "Contoso Widget Manager",
})
@versionedDependency(Azure.Core.Versions.v1_0_Preview_2)
namespace Contoso.WidgetManager;

See the sections Versioning your service and Using Azure.Core versions for more details about service versioning.

NOTE: The Azure.Core version used in this tutorial may be out of date! The cadl-azure-core README.md file contains the versions listing which describes the available versions.

Defining your first resource

Now we're ready to describe our first resource type. A "resource" is a model type that represents a fundamental type in the domain model of your service.

For our WidgetService, the most obvious model type that we will need is called Widget. We can create it by creating a model that is annotated with the @resource decorator. Add the following lines after the top-level namespace declaration:

@doc("A widget.")
@resource("widgets")
model Widget {
  @key("widgetName")
  @doc("The widget name.")
  @visibility("read")
  name: string;

  @doc("The ID of the widget's manufacturer.")
  manufacturerId: string;
}

There are a few important things to point out here:

  • The Widget model has a @resource decorator with a parameter of "widgets". This string is the "collection name" of the resource and affects where the resource appears in the service URI layout.
  • The name property has a @key decorator with a parameter of "widgetName". This string customizes the name of the path parameter (and the parameter name itself) in operations that use this resource type. There must be one property with a @key decorator on all resource types!
  • The @visibility("read") decorator on the name property says that the name property should only appear in operation responses but not in operations that allow you to change properties of the Widget resource.
  • We use @doc decorators on the model type and all properties to describe. Documentation strings are enforced by linting rule when authoring specs with Azure.Core!

Great, now we have a resource type! Now, how do we define operations for this resource?

Defining standard resource operations

The Azure.Core namespace provides a number of standard lifecycle operations for resource types which encode many of the requirements of the Azure REST API Guidelines. Let's define the standard set of CRUD (Create, Read, Update, Delete) operations that are typically needed for a resource type in an Azure service.

We will do that by defining an interface called Widgets which reuses the standard operation shapes for these lifecycle operations:

interface Widgets {
  @doc("Fetch a Widget by name.")
  getWidget is ResourceRead<Widget>;

  @doc("Creates or updates a Widget.")
  createOrUpdateWidget is ResourceCreateOrUpdate<Widget>;

  @doc("Delete a Widget.")
  deleteWidget is ResourceDelete<Widget>;

  @doc("List Widget resources.")
  listWidgets is ResourceList<Widget>;
}

NOTE: It is not necessary to define your resource operations inside of an interface. You can also define them in a sub-namespace of your service or inside the top-level namespace of the service. However, it is a best practice in Cadl to use interface to encapsulate the operations of a particular resource type.

This interface uses the following standard lifecycle operations:

  • ResourceRead<TResource> - defines a "read" operation for a single resource instance
  • ResourceCreateOrUpdate<TResource> - defines an "upsert" operation which either creates or updates an instance of the resource type depending on whether it already exists
  • ResourceDelete<TResource> - defines a "delete" operation to delete a specific instance of the resource
  • ResourceList<TResource> - defines an operation that lists all instances of the resource type

NOTE: There are both instantaneous and long-running versions of "create", "update", and "delete" operations for resource types depending on what you need for a particular resource!

Based on the configuration of the Widget type and the use of these standard operation templates, these operations will all exist under the route path:

/widgets/{widgetName}

The list operation will simply generate the path /widgets.

Defining long-running resource operations

If your service uses any long-running operations (those that usually take longer than 1 second to complete), you will need to define a "status monitor" operation which can report the status of the operation as it proceeds.

Let's say that we want to make our createOrUpdateWidget and deleteWidget operations long-running; here's how we can update our Widgets interface to accomplish that:

interface Widgets {
  @doc("Gets status of a Widget operation.")
  getWidgetOperationStatus is GetResourceOperationStatus<Widget>;

  @doc("Fetch a Widget by name.")
  getWidget is ResourceRead<Widget>;

  @doc("Creates or updates a Widget asynchronously.")
  @pollingOperation(Widgets.getWidgetOperationStatus)
  createOrUpdateWidget is LongRunningResourceCreateOrUpdate<Widget>;

  @doc("Delete a Widget asynchronously.")
  @pollingOperation(Widgets.getWidgetOperationStatus)
  deleteWidget is LongRunningResourceDelete<Widget>;

  @doc("List Widget resources.")
  listWidgets is ResourceList<Widget>;
}

First, we change createOrUpdateWidget to use LongRunningResourceCreateOrUpdate<Widget> and deleteWidget to use LongRunningResourceDelete.

Next, we define the getWidgetOperationStatus operation based on the GetResourceOperationStatus signature. This defines the operation status monitor as a child resource of the Widget type so that it shows up under that resource in the route hierarchy.

Finally, we must add the pollingLocation decorator to both of the long-running operations and reference the Widgets.getWidgetOperationStatus operation. This connects the long-running operations to their associated status monitor operation to make it easier for service clients to be generated.

NOTE: The status monitor operation must be defined earlier in the interface than the long-running operations that reference it otherwise Cadl will not be able to resolve the reference!

Defining child resources

Sometimes your resource types will need to have child resources that relate to their parent types. You can identify that a resource type is the child of another resource by using the @parentResource decorator.

For example, here's how you could create a new WidgetPart resource under the Widget defined above:

@doc("A WidgetPart resource belonging to a Widget resource.")
@resource("parts")
@parentResource(Widget)
model WidgetPart {
  @key("partName")
  name: string;

  @doc("The part number.")
  number: string;

  @doc("The part name.")
  partName: string;
}

When you use the standard resource operations with child resource types, their operation routes will include the route of the parent resource. For example, we might define the following operations for WidgetPart:

@doc("Creates a WidgetPart")
createWidgetPart is ResourceCreateWithServiceProvidedName<WidgetPart>;

@doc("Get a WidgetPart")
getWidgetPart is ResourceRead<WidgetPart>;

These operations will be defined under the route path:

/widgets/{widgetName}/parts/{partName}

Defining custom resource actions

Often your resource types will need additional operations that are not covered by the standard resource operation shapes. For this, there are a set of operation signatures for defining resource actions at the instance and collection level.

To define a custom action you can use the ResourceAction and ResourceCollectionAction signatures from Azure.Core. Let's define a couple of custom actions for the Widget and WidgetPart resources:

// In the Widgets interface...
@doc("Schedule a widget for repairs.")
scheduleRepairs is ResourceAction<
  Widget,
  WidgetRepairRequest,
  WidgetRepairRequest
>;

// In the WidgetParts interface...
@doc("Reorder all parts for the widget.")
reorderParts is ResourceCollectionAction<
  WidgetPart,
  WidgetPartReorderRequest,
  WidgetPartReorderRequest
>;

The scheduleRepairs operation defines a custom action for all instances of the Widget resource. All collection action templates expect 3 parameters: the resource type, the request action parameters, and the response type. In this case, WidgetRepairRequest is both the parameter and response type because we are using it as the body of both the request and the response of this operation.

NOTE: The request parameters and response type do not have to be the same type!

We also define an collection operation called reorderParts. Similarly to scheduleRepairs, it uses the WidgetPartReorderRequest as the request and response body.

Here are what the routes of these two operations will look like:

/widgets/{widgetName}:scheduleRepairs
/widgets/{widgetName}/parts:reorderParts

Notice that the operation name is used as the action name in the route!

There are also long-running operation versions of these two operations:

  • LongRunningResourceAction
  • LongRunningResourceCollectionAction

The same rules described in the long-running operations section also apply to these long-running action signatures.

Customizing operation parameters and responses

For all standard lifecycle operations (excluding resource custom actions, those are already customizable) you can customize the operation parameters and response body by passing a special model type to the TCustom parameter of the operation template, typically the second parameter of the operation template.

For example, if you wanted to add standard list operation query parameters to the listWidgets operation, you could modify the definition like this:

@doc("List Widget resources")
listWidgets is ResourceList<
  Widget,
  {
    parameters: StandardListQueryParameters & SelectQueryParameter;
  }
>;

This definition populates the ResourceList<TResource, TCustom> signature with an inline model definition for the TCustom type. The TCustom parameter expects a model with two possible properties:

  • parameters - A model type that will be mixed into the parameter list of the operation. Typically used for mixing in @query or @header parameters for the operation.
  • response - A model type that will be mixed into the non-error response envelope of the operation. This can be used to inject response properties or @headers in your operation response.

An example of customizing both parameters and response at the same time would be using the OASIS Repeatable Requests customization types RepeatabilityRequestHeaders and RepeatabilityResponseHeaders. Here's how we can use these types with the deleteWidget operation:

@doc("Delete a Widget.")
deleteWidget is ResourceDelete<
  Widget,
  {
    parameters: RepeatabilityRequestHeaders;
    response: RepeatabilityResponseHeaders;
  }
>;

This would customize both the request parameters and the response envelope of the deleteWidget operation to include the headers defined in both these model types.

Versioning your service

It is inevitable that service specifications will change over time. It is a best practice to add versioning support to your specification from the first version. To do that, you will need to define an enum containing your service versions and then apply the @versioned decorator to your service namespace.

Here is an example for the WidgetManager service:

@service({
  title: "Contoso Widget Manager",
})
@versioned(Contoso.WidgetManager.Versions)
@versionedDependency(
  [[Contoso.WidgetManager.Versions.v2022_08_31, Azure.Core.Versions.v1_0_Preview_2]]
)
namespace Contoso.WidgetManager;

enum Versions {
  v2022_08_31: "2022-08-31",
}

There are a few things to point out here:

  • We define an enum called Versions inside of the service namespace. For each service version, we map a version symbol like v2022_08_31 to a version string like 2022-08-31. This service currently only has a single version, but we can add more to this enum as things change over time.
  • We add the @versioned decorator and reference the Versions enum we defined using the fully-qualified name Contoso.WidgetManager.Versions. This marks the service as being versioned and specifies the set of versions.
  • We change the @versionedDependency decorator we used previously to now link each service version to a specific version of Azure.Core. See the Using Azure.Core Versions section for more information.

Imagine that it's 3 months later and you want to release a new version of your service with some slight changes. Add a new version to the Versions enum:

enum Versions {
  v2022_08_31: "2022-08-31",
  v2022_11_30: "2022-11-30",
}

You will also need to change the @versionedDependency decorator:

@versionedDependency(
  [
    [Contoso.WidgetManager.Versions.v2022_08_31, Azure.Core.Versions.v1_0_Preview_2],
    [Contoso.WidgetManager.Versions.v2022_11_30, Azure.Core.Versions.v1_0_Preview_2]
  ]
)

Finally, you can express changes to your service using the @added and @removed decorators. Here's an example of adding a new property to Widget and removing an old one:

@doc("A widget.")
@resource("widgets")
model Widget {
  @key("widgetName")
  @doc("The widget name.")
  @visibility("read")
  name: string;

  @doc("The widget color.")
  @added(Contoso.WidgetManager.Versions.v2022_11_30)
  color: string;

  @doc("The ID of the widget's manufacturer.")
  @removed(Contoso.WidgetManager.Versions.v2022_11_30)
  manufacturerId: string;
}

You can do a lot more with versioning decorators, so consult the cadl-versioning README.md for more information on how you can use them to annotate your service and describe changes between different versions.

Using Azure.Core versions

cadl-azure-core is a versioned Cadl library. This means that even as the Cadl portions of the cadl-azure-core library are updated, you can anchor each version of your spec to a specific Azure.Core version. This is done by decorating your service namespace with the @versionedDependency decorator from the cadl-versioning library.

Simple Cadl specs need only pass the desired Azure.Core version into the @versionedDependency decorator:

@service({
  title: "Contoso Widget Manager",
})
@versionedDependency(Azure.Core.Versions.v1_0_Preview_2)
namespace Contoso.WidgetManager;

If your spec has multiple versions, you will need to specify the version of cadl-azure-core that was used for each version in your spec. Assuming that there are two versions of Azure.Core and each version of your service uses a different one, it would look like this:

@service({
  title: "Contoso Widget Manager",
})
@versioned(Contoso.WidgetManager.Versions)
@versionedDependency(
  [
    [Contoso.WidgetManager.Versions.v2022_08_31, Azure.Core.Versions.v1_0_Preview_2],
    [Contoso.WidgetManager.Versions.v2022_11_30, Azure.Core.Versions.v1_0_Preview_2]
  ]
)
namespace Contoso.WidgetManager;

Advanced Topics

Once you have written your first service with Azure.Core, you might be interested to try the following features:

Defining singleton resources

You can define a singleton resource (a resource type with only one instance) by using a string literal for the key type. Imagine we want to expose an analytics endpoint for each Widget instance. Here's what it would look like:

@resource("analytics")
@parentResource(Widget)
model WidgetAnalytics {
  @key("analyticsId")
  id: "current";

  @doc("The number of uses of the widget.")
  useCount: int64;

  @doc("The number of times the widget was repaired.")
  repairCount: int64;
}

You can then use the standard operation signatures with this singleton resource type:

op getAnalytics is ResourceRead<WidgetAnalytics>;
op updateAnalytics is ResourceCreateOrUpdate<WidgetAnalytics>;

By using a literal value of "current" for "id", the route path for these operations will be the following:

"/widgets/{widgetName}/analytics/current"

The operations defined against this singleton resource will also exclude the key parameter because it cannot be changed.

Library Versions

This is a versioned Cadl library which means that you must add the @versionedDependency decorator to your service namespace when you use its contents.

Here are the current versions:

  • Azure.Core.Versions.v1_0_Preview_2

See the Using Azure.Core Versions section for more details on how to use the @versionedDependency decorator.

Library Tour

The @azure-tools/cadl-azure-core library defines the following artifacts:

Models

The @azure-tools/cadl-azure-core library defines the following models:

Model Notes
Page<TResource> Model for a paged resource. <TResource> is the model description of the item.
PagedResultMetadata Contains the metadata associated with a Page<TResource>.
RequestParameter<T> For long-running operations, identifies that a continuation operation request parameter is pulled from the original request. <T> is the property name on the original request.
ResponseProperty<T> For long-running operations, identifies that a continuation operation request parameter is pulled from the response of the previous request. <T> is the property name on the previous response.
LongRunningStates Identifies the long-running states associated with a long-running operation.
OperationLinkMetadata Contains the metadata associated with an operation link.

Operations

The @azure-tools/cadl-azure-core library defines these standard operation templates as basic building blocks that you can expose. You can use is to compose the operations to meet the exact needs of your APIs.

For all of these operation templates, TResource is the resource model and TCustom allows customization of the operation parameters or response. TCustom, if provided, must extend the Azure.Core.Foundations.CustomizationFields model, which looks like:

@doc("The expected shape of model types passed to the TCustom parameter of operation signatures.")
model CustomizationFields {
  @doc("An object containing custom parameters that will be included in the operation.")
  parameters?: object;

  @doc("An object containing custom properties that will be included in the response.")
  response?: object;
}
Operation Notes
ResourceCreateOrUpdate<TResource, Traits> Creates an instance of the resource or updates the existing instance.
ResourceCreateOrReplace<TResource, Traits> Creates an instance of the resource or entirely replaces the existing instance.
ResourceCreateWithServiceProvidedName<TResource, Traits> Creates an instance of the resource while allowing the service to choose the name.
ResourceRead<TResource, Traits> Reads the details of an existing instance of the resource.
ResourceDelete<TResource, Traits> Deletes an existing instance of the resource.
ResourceList<TResource, Traits> Lists instances of the resource type with server-driven paging.
ResourceUpdate<TResource, Traits> Updates an existing instance of the resource.
NonPagedResourceList<TResource, Traits> Resource LIST operation without paging.
ResourceAction<TResource, TParams, TResponse, Traits> Perform a custom action on a specific resource.
ResourceCollectionAction<TResource, TParams, TResponse, Traits> Perform a custom action on a collection of resources.
LongRunningResourceCreateOrReplace<TResource, Traits> Long-running version of the ResourceCreateOrReplace operation.
LongRunningResourceCreateOrUpdate<TResource, Traits> Long-running version of the ResourceCreateOrUpdate operation.
LongRunningResourceCreateWithServiceProvidedName<TResource, Traits> Long-running version of the ResourceCreateWithServieProvidedName operation.
LongRunningResourceDelete<TResource, Traits> Long-running version of the ResourceDelete operation.
LongRunningResourceUpdate<TResource, Traits> Long-running version of the ResourceUpdate operation.

Decorators

The @azure-tools/cadl-azure-core library defines the following decorators:

Declarator Scope Usage
@pagedResult models indicates model describes a paged result.
@items model properties indicates model property that stores the items within a paged result.
@nextLink model properties indicates model property that contains the continuation information for the next page.
@nextPageOperation operations indicates operation that will be called for subsequent page requests.
@lroStatus enums and model properties indicates model or model property that represents long-running operation status.
@lroSucceeded enum members indicates enum member that corresponds to the long-running operation succeeded status.
@lroCanceled enum members indicates enum member that corresponds to the long-running operation canceled status.
@lroFailed enum members indicates enum member that corresponds to the long-running operation failed status.
@pollingLocation model properties indicates model property that contains the location to poll for operation state.
@finalLocation model properties indicates model property that contains the final location for the operation result.
@operationLink operations indicates operation that is linked to the decorated operation by virtue of its linkType.
@pollingOperation operations indicates an operation is a polling operation for a long-running operation.
@finalOperation operations indicates an operation is the final operation for a long-running operation.
@fixed enum indicates that an enum should not be treated as extensible, since Azure assumes are enums are extensible.

API

The @azure-tools/cadl-azure-core library defines the following API functions that emitter authors can use for development:

Name Entity Returns Description
getPagedResult models and operations PagedResultMetadata? Returns the PagedResultMetadata if associated with a model or operation return type.
getItems model properties boolean Returns true if the model property is annotated with @items.
getNextLink model properties boolean Returns true if the model property is annotated with @nextLink.
getLongRunningStates enums, models and model properties LongRunningStates? Returns the LongRunningStates associated with an entity.
isLroSucceededState enum members boolean Returns true if the enum member represents a "succeeded" state.
isLroCanceledState enum members boolean Returns true if the enum member represents a "canceled" state.
isLroFailedState enum members boolean Returns true if the enum member represents a "failed" state.
isPollingLocation model properties boolean Returns true if the model property is annotated with @pollingLocation.
isFinalLocation model properties boolean Returns true if the model property is annotated with @finalLocation.
getOperationLink operations OperationLinkMetadata? Returns the OperationLinkMetadata for an operation with a specific linkType.
getOperationLinks operations Map<string, OperationLinkMetadata>? Returns a Map of OperationLinkMetadata objects for an Operation where the key is the linkType.
isFixed enum boolean Returns true if the enum is annotated with @fixed.

Generating an OpenAPI Specification

To generate an OpenAPI v2 (Swagger) specification from the service definition, run the following command inside of the project folder:

cadl compile . --emit @azure-tools/cadl-autorest

This will create a file in the cadl-output subfolder called openapi.json.

You can learn more about the cadl-autorest emitter and its options by reading its README.md.

A Complete Example

A complete version of the Contoso.WidgetManager specification can be found in the widget-manager sample folder.

You can experiment with the full sample on the Cadl Azure Playground!

Readme

Keywords

Package Sidebar

Install

npm i @azure-tools/cadl-azure-core

Weekly Downloads

23

Version

0.26.0

License

MIT

Unpacked Size

243 kB

Total Files

77

Last publish

Collaborators

  • microsoft1es
  • azure-sdk