Skip to content

TypeSpec Type Representation in TCGC

This page documents what type definitions in TypeSpec would look like when returned by TCGC

Main TypeSpec Code Example

import "@typespec/http";
using TypeSpec.Http;
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "api-key">)
@service({
title: "Contoso Widget Manager",
})
@server(
"{endpoint}/widget",
"Contoso Widget APIs",
{
endpoint: string,
}
)
namespace Contoso.WidgetManager;
model Widget {
@visibility("read", "update")
@path
id: string;
weight: int32;
color: "red" | "blue";
}
@error
model Error {
code: int32;
message: string;
}
@route("/widgets")
@tag("Widgets")
interface Widgets {
@get list(): Widget[] | Error;
@get read(@path id: string): Widget | Error;
@post create(...Widget): Widget | Error;
@patch update(...Widget): Widget | Error;
@delete delete(@path id: string): void | Error;
@route("{id}/analyze") @post analyze(@path id: string): string | Error;
}

Overall Hierarchy of Types

This is a rough sketch of the hierarchy. We’ll go into more detail for each section, this is just to get an idea of the overall structure.

const sdkContext = {
sdkPackage: {
clients: [
{
kind: "client",
name: "WidgetClient",
initialization: {
properties: [
{
kind: "endpoint",
type: {
kind: "union",
values: [
{
kind: "endpoint",
serverUrl: "{endpoint}",
templateArguments: [
{
name: "endpoint",
kind: "path",
...
}
]
},
{
kind: "endpoint",
serverUrl: "{endpoint}/widget",
templateArguments: [
{
name: "endpoint",
kind: "path",
...
}
]
}
]
}
},
{
kind: "credential",
...
}
]
},
methods: [
{
kind: "clientaccessor",
response: {
name: "WidgetPartsClient",
kind: "client",
...
}
},
{
kind: "basic",
parameters: [
{
name: "id",
kind: "method",
type: {
kind: "string",
},
...
}
],
operation: {
kind: "http",
verb: "get",
parameters: [
{
name: "id",
kind: "path",
type: {
kind: "string",
},
correspondingMethodParams: [
{
name: "id",
kind: "method",
type: {
kind: "string",
},
...
}
]
}
],
bodyParam: undefined,
responses: {
200: {
kind: "http",
...
}
},
examples: [
{
kind: "http",
name: "Example 1",
description: "Example 1",
filePath: "example1.json",
rawExample: {...},
parameters: [...],
responses: {...}
}
]
},
response: {
kind: "method",
logicalPath: undefined,
...
},
exception: {
kind: "method",
...
},
...
}
],
...
}
],
models: [
{
kind: "model",
name: "Widget",
properties: [
{
kind: "path",
name: "id",
type: {
kind: "string",
},
...
},
{
kind: "property",
name: "color",
type: {
kind: "enum",
isFixed: true,
...
},
...
}
]
}
],
enums: [
{
name: "WidgetColor",
generatedName: true,
values: [
{
value: "red",
...
},
{
value: "blue",
...
}
]
}
],
...
},
...
}

SdkContext

SdkContext is the context of your SDK emitter.

Interface

export interface SdkContext<
TOptions extends object = Record<string, any>,
TServiceOperation extends SdkServiceOperation = SdkHttpOperation,
> {
// emit context from tsp that is used to create our TCGC Sdk Context
emitContext: EmitContext<TOptions>;
// representation of the entire package the language emitter should generate
sdkPackage: SdkPackage<TServiceOperation>;
program: Program;
emitterName: string;
}

Usage

It is used to persist information across functions. You should never define global constants or variables in your emitter code, because this leads to issues with multiple emitter runs. Instead, all global information should be stored in your context.

You should define your own language-specific SdkContext that extends from the one defined in @azure-tools/typespec-client-generator-core. This is where you keep your global variables. You should utilize the createSdkContext from tcgc. You also shouldn’t instantiate your own interface, instead you should call createSdkContext to create.

import { EmitContext } from "@typespec/compiler";
import { createSdkContext, SdkServiceOperation } from "@azure-tools/typespec-client-generator-core";
import { PythonEmitterOptions } from "./lib.js";
interface PythonSdkContext<TServiceOperation extends SdkServiceOperation>
extends SdkContext<PythonEmitterOptions, TServiceOperation> {
ClientMap: Map<SdkClientType, PythonSdkClientType>;
}
export async function $onEmit(context: EmitContext<PythonEmitterOptions>) {
const sdkContext: PythonSdkContext = {
...createSdkContext(context, "@azure-tools/typespec-python"),
ClientMap: new Map<sdkClientType, PythonSdkClientType>(),
};
}

SdkPackage

SdkPackage represents the entire package that your language emitter should generate. It synthesizes all of the information language emitters will need to generate the entire package

Interface

export interface SdkPackage<TServiceOperation extends SdkServiceOperation> {
name: string;
rootNamespace: string;
clients: SdkClientType<TServiceOperation>[];
models: SdkModelType[];
enums: SdkEnumType[];
diagnostics: readonly Diagnostic[];
}

Example

const sdkPackage = {
name: "ContosoWidgetManager",
rootNamespace: "Contoso.WidgetManager",
clients: [
{
name: "ContosoWidgetManagerClient",
kind: "client",
...
}
],
models: [
{
name: "Widget",
kind: "model",
...
},
{
name: "Error",
kind: "model",
...
}
],
enums: [],
diagnostics: [],
}

Usage

const sdkPackage = sdkContext.sdkPackage;

SdkClientType

An SdkClientType represents a single client in your package.

All clients will have an initialization property. Whether that property’s access is internal or public will determine whether your client can be publicly instantiated or not.

Interface

export interface SdkClientType<TServiceOperation extends SdkServiceOperation> {
name: string;
kind: "client";
description?: string;
details?: string;
initialization: SdkInitializationType;
apiVersions: string[];
// fully qualified. Compare to sdkPackage.rootNamespace to figure out where to generate
nameSpace: string;
methods: SdkMethod<TServiceOperation>[];
}

Example

const sdkClient = {
name: "ContosoWidgetManagerClient";
kind: "client";
initialization: {
name: "WidgetManagerOptions",
kind: "model",
access: "public",
...
};
apiVersions: [],
nameSpace: "Contoso.WidgetManager",
methods: [
{
name: "getWidgets",
kind: "clientaccessor",
response: {
name: "Widgets",
kind: "client",
initialization: {
kind: "model",
access: "internal",
},
apiVersions: [],
nameSpace: "Contoso.WidgetManager",
methods: [
{
name: "list",
kind: "method",
...
},
{
name: "read",
kind: "method",
...
}
]
}
}
],
}

Usage

import { SdkClientType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkServiceOperation } from "./interfaces.js";
import { get }
const serializedClients: PythonSdkClientType[] = [];
function serializeClient<
TServiceOperation extends PythonSdkServiceOperation
>(
sdkContext: PythonSdkContext<TServiceOperation>,
client: SdkClientType<TServiceOperation>,
): PythonSdkClientType {
// Map the information from the SdkClientType to your language's interface for a client.
// Would recommend that eventually your language's client type interface extends from
// SdkClientType, and just adds your language-specific information on top.
const pythonClient = {
...client,
parameters: (client.initialization.properties.map(
(x) => getSdkModelPropertyType(
context, x
)
) : undefined
),
methods: client.methods.filter(
(x) => x.kind === "method"
).map(
(x) => serializeServiceMethod(sdkContext, x)
),
subclients: [],
}
sdkContext.ClientMap.set(client, pythonClient)
return pythonClient
}
for (const client of sdkPackage.clients) {
serializedClients.push(serializeClient(sdkContext, client));
}

SdkInitializationType

Initialization model for a client. Whether it’s access is public or internal tells you whether the client is publicly instantiable.

Interface

export interface SdkInitializationType extends SdkModelType {
// properties takes care of all of the initialization info that you will need.
properties: (SdkEndpointParameter | SdkCredentialParameter | SdkMethodParameter)[];
}

Example

const sdkInitializationType = {
name: "WidgetManagerOptions",
kind: "model",
properties: [
{
kind: "endpoint",
serverUrl: "{endpoint}/widget",
templateArguments: [
{
kind: "path",
name: "endpoint",
type: {
kind: "string",
},
},
],
},
{
kind: "credential",
type: {
kind: "credential",
scheme: {
kind: "apiKey",
in: "header",
name: "api-key",
},
},
onClient: true,
},
],
crossLanguageDefinitionId: "Contoso.WidgetManager.WidgetManagerOptions",
apiVersions: [],
usage: UsageFlags.Input,
access: "public",
isFormDataType: false,
isError: false,
};

Usage

The usage of this property is more language-dependent.

Some emitters will create a model and have the client accept the model as options to initialize your client. Others will flatten out the parameters and accept them individually as parameter input.

SdkMethod

An SdkMethod is any of the types of methods that can be a method on a client.

There are two main types of an SdkMethod:

  1. SdkClientAccessor returns a subclient of the client it’s on
  2. SdkServiceMethod wraps a service call
export type SdkMethod<TServiceOperation extends PythonSdkServiceOperation> =
| SdkServiceMethod<TServiceOperation>
| SdkClientAccessor<TServiceOperation>;

They each extend from the shared SdkMethodBase

interface SdkMethodBase<TServiceOperation extends SdkServiceOperation> {
__raw?: Operation;
name: string;
access: AccessFlags;
parameters: SdkParameter[];
apiVersions: string[];
description?: string;
details?: string;
}

Below we go into each method type

SdkClientAccessor

A clientaccessor method is simply a method on a client that returns another client. The returned client can be instantiable or un-instantiable. If the returned client is instantiable, most likely your clientaccessor will be part of the public api surface, so users can instantiate the subclient using your client method. If it is not instantiable, it is up to you how to expose the subclient on your current client for users to access.

Interface

interface SdkClientAccessor<TServiceOperation extends SdkServiceOperation>
extends SdkMethodBase<TServiceOperation> {
kind: "clientaccessor";
response: SdkClientType<TServiceOperation>;
}

Example

const sdkClientAccessor = {
name: "getWidgets",
kind: "clientaccessor",
response: {
name: "Widgets",
kind: "client",
initialization: undefined,
...
}
}

Usage

Our strong recommendation is you build up a list of clients by iterating through sdkPackage.clients first, and to not recurse into creating the subclients. Only once you’ve processed each client, then you go through and link clients to their subclients. Not doing recursion prevents confusion over where you are in the call chain.

import { SdkServiceOperation, SdkClientType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { PythonSdkClientType } from "./interfaces.js";
function linkSubClients<TServiceOperation extends PythonSdkServiceOperation>(
sdkContext: PythonSdkContext<TServiceOperation>,
): void {
for (const client of sdkContext.clients) {
client.subclients = client.methods
.filter((x) => x.kind === "clientaccessor")
.map((x) => x.returnType)
.map((x) => sdkContext.ClientMap.get(x));
}
}

SdkServiceMethod

An SdkServiceMethod is any service method on a client that calls the service.

The actual service call is a separate property on the method, .operation. This way, our service methods are able to abstract away the protocol used to call the service (i.e. http or gRPC)

All SdkServiceMethods share the following base:

interface SdkServiceMethodBase<TServiceOperation extends SdkServiceOperation>
extends SdkMethodBase<TServiceOperation> {
// This represents the operation request we need to send to the service.
// This helps abstract away the transport
operation: TServiceOperation;
// The parameters you generate on the method signature that users will interact with.
// These aren't tied to any protocol, so there are no 'header' / 'path' parameters etc.
parameters: SdkMethodParameter[];
response: SdkMethodResponse;
exception?: SdkMethodResponse;
}
function serializeServiceMethod<TServiceOperation extends PythonSdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>,
): PythonSdkServiceMethod {
switch (method.kind) {
case "basic":
return serializeBasicServiceMethod(context, method);
case "paging":
return serializePagingServiceMethod(context, method);
case "lro":
return serializeLroServiceMethod(context, method);
case "lropaging":
return serializeLroPagingServiceMethod(context, method);
}
}

SdkBasicServiceMethod

This models a basic service call that is synchronous server side.

Interface
export interface SdkBasicServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "basic";
}
Example
const sdkBasicServiceMethod = {
kind: "basic",
__raw?: {
// Original Tsp Operation type
kind: "Operation",
},
name: "read",
access: "public",
apiVersions: [],
parameters: [
{
name: "id",
kind: "method",
...
},
],
operation: {
kind: "http",
...
},
response: {
kind: "method",
type: {
kind: "model",
name: "Widget",
}
},
}
Usage
function serializeBasicServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkBasicServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation)
},
}

SdkPagingServiceMethod

This represents a paging method we will generate on the client. It includes an initial service operation call, and potentially a next link operation as well.

We currently only have paging method support for azure-generated sdks.

Interface
export interface SdkPagingServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "paging";
// raw paging information returned from getPagedResult
__raw_paged_metadata: PagedResultMetadata;
// string to get to next link. If undefined, we are doing fake paging
nextLinkPath?: string;
// service operation if separate requests need to be made for subsequent paging
nextLinkOperation?: TServiceOperation;
}
Example
import "@azure-tools/typespec-azure-core";
using Azure.Core;
using Azure.Core.Traits;
// Skipping past service definitions
alias Operations = Azure.Core.ResourceOperations<SupportsRepeatableRequests>;
listWidgets is Operations.ResourceList<Widget>;
const sdkPagingServiceMethod = {
kind: "paging",
__raw?: {
// Original Tsp Operation type
kind: "Operation",
},
__raw_paged_metadata: {
// Raw output from getPagedMetadata
}
name: "listWidgets",
access: "public",
apiVersions: [],
parameters: [],
operation: {
kind: "http",
...
},
response: {
kind: "method",
type: {
kind: "model",
name: "Widget",
},
// we want to return the `.value` part of the service response to users
pathFromService: "value",
},
nextLinkPath: "nextLink",
nextLinkOperation: undefined,
}
Usage
function serializePagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkPagingServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}

SdkLroServiceMethod

Represents an LRO method we want to generate on the client.

Only returned for azure-generated sdks

Interfaces
export interface SdkLroServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "lro";
// raw LroMetadata returned from azure-core helper function getLroMetadata
__raw_lro_metadata: LroMetadata;
// initial call to begin LRO polling
// thinking of instead making it the regular operation property so we can continue using that property
operation: TServiceOperation;
}

Example

import "@azure-tools/typespec-azure-core";
using Azure.Core;
using Azure.Core.Traits;
// Skipping past service definitions
alias Operations = Azure.Core.ResourceOperations<SupportsRepeatableRequests>;
// Operation Status
/** Gets status of a Widget operation. */
getWidgetOperationStatus is Operations.GetResourceOperationStatus<Widget>;
// Widget Operations
/** Creates or updates a Widget asynchronously */
@pollingOperation(getWidgetOperationStatus)
createOrUpdateWidget is Operations.LongRunningResourceCreateOrUpdate<Widget>;
const sdkLroServiceMethod = {
kind: "lro",
__raw?: {
// Original Tsp Operation type
kind: "Operation",
},
__raw_lro_metadata: {
// Raw output from getLroMetadata
}
name: "createOrUpdateWidget",
access: "public",
apiVersions: [],
parameters: [],
operation: {
kind: "http",
...
},
response: {
kind: "method",
type: {
kind: "model",
name: "Widget",
}
},
}
Usage
function serializeLroServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroServiceMethod<TServiceOperation>,
): PythonSdkLroServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method),
},
}

SdkLroPagingServiceMethod

This is a combination of Lro and paging. We start off with an LRO call to the service, and then the response is returned to us in pages. Also only available for azure-sdks.

Interface
export interface SdkLroPagingServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "lropaging";
__raw_lro_metadata: LroMetadata;
// initial call to start LRO
operation: SdkServiceOperation;
__raw_paged_metadata: PagedResultMetadata;
nextLinkPath?: string; // off means fake paging
nextLinkOperation?: SdkServiceOperation;
}
Usage
function serializeLroPagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroPagingServiceMethod<TServiceOperation>,
): PythonSdkLroPagingServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}

SdkServiceOperation

One main part of this design is we’ve decoupled the actual service operation call from the method we generate on our client. This is because we want to abstract away the protocol used to call our service. Additionally, while there is a high level of correlation between the method parameters we intake from SDK users, and the parameters we end up passing the service, it’s not one-to-one. We need to serialize the method parameters and additionally, when parameters are spread or grouped together, it requires us to do mapping between which method parameters correspond to which service parameters.

We currently only support HTTP calls to the service.

Interface

export interface SdkHttpOperation {
kind: "http";
// raw HTTP operation output from typespec/http
__raw: HttpOperation;
// route path for the target operation. TypeSpec suggest to use `uriTemplate` instead as `path` will not work for complex cases like not-escaping reserved chars.
path: string;
// the fully resolved URI template for the target operation as defined by [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570).
uriTemplate: string;
verb: HttpVerb;
parameters: (SdkPathParameter | SdkQueryParameter | SdkHeaderParameter)[];
bodyParam: SdkBodyParameter;
// mapping of status codes to SdkHttpResponse for valid responses
// HttpStatusCodeRange can represent either a single status code or a range.
responses: SdkHttpResponse[];
exceptions: SdkHttpResponse[];
examples?: SdkHttpOperationExample[];
}

Example

const sdkHttpOperation = {
kind: "http",
__raw: {
// raw HttpOperation from @typespec/http;
},
path: "/widgets",
uriTemplate: "/widgets",
verb: "get",
parameters: [
{
kind: "path",
name: "id",
...
}
],
bodyParam: undefined,
responses: {
200: {
kind: "http",
...
},
},
exceptions: {
"*": {
kind: "http",
...
}
},
examples: [
{
kind: "http",
name: "Example 1",
description: "Example 1",
filePath: "example1.json",
rawExample: {...},
parameters: [...],
responses: {...}
}
]
}

Usage

function serializeServiceOperation<TServiceOperation extends PythonSdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>,
operation: TServiceOperation,
): TServiceOperation {
switch (operation.kind) {
case "http":
return serializeHttpServiceOperation(context, method, operation);
}
}

Parameters and Properties

Like the TypeSpec concept of a ModelProperty, all properties and parameters are part of the SdkModelPropertyType. They all extend from SdkModelPropertyTypeBase.

export type SdkModelPropertyType =
| SdkMethodParameter
| SdkEndpointParameter
| SdkCredentialParameter
| SdkBodyModelPropertyType
| SdkQueryParameter
| SdkPathParameter
| SdkHeaderParameter
| SdkBodyParameter;
interface SdkModelPropertyTypeBase {
__raw?: ModelProperty;
type: SdkType;
name: string;
description?: string;
details?: string;
apiVersions: string[];
onClient: boolean;
// clientDefaultValue only exists for api versions
clientDefaultValue?: any;
isApiVersionParam: boolean;
optional: boolean;
nullable: boolean;
}

Method parameters

These are parameters to client initialization and method son the client. These will be the parameters that SDK users directly use. They will eventually be mapped to

SdkEndpointParameter

An SdkEndpointParameter represents a parameter to a client’s endpoint.

TCGC will always give it to you as overridable:

If the server URL is a constant, we will return a templated endpoint with a default value of the constant server URL. In the case where the endpoint has extra template arguments, the type is a union of a completely-overridable endpoint, and an endpoint that accepts template arguments. If there are multiple servers, we will return the union of all of the possibilities.

export interface SdkEndpointParameter extends SdkModelPropertyTypeBase {
kind: "endpoint";
urlEncode: boolean;
onClient: true;
serializedName?: string;
type: SdkEndpointType | SdkUnionType<SdkEndpointType>;
}

SdkCredentialParameter

Parameter for credential input to clients.

export interface SdkCredentialParameter extends SdkModelPropertyTypeBase {
kind: "credential";
// can be either an SdkCredentialType or a union
// of different credential types
type: SdkCredentialType | SdkUnionType;
onClient: true;
}

SdkMethodParameter

Represents a parameter to a client method. Does not have any transport (i.e. HTTP) related information on it. This is solely meant to represent information that we expect sdk users to pass in. We then take care to map the method input and create our request to the service using the information users have inputted.

export interface SdkMethodParameter extends SdkModelPropertyTypeBase {
kind: "method";
}

Service Parameters

Currently we only support HTTP service parameters.

SdkQueryParameter

This represents an HTTP query parameter.

// different collection format to use to separate each value in array
// - multi: no separator, treat each value in array as a separate query parameter
// - csv: , as separator
// - ssv: space as separator
// - tsv: tab as separator
// - pipes: | as separator
// - simple: , as separator
// - form: no separator, treat each value in array as a separate query parameter
export type CollectionFormat = "multi" | "csv" | "ssv" | "tsv" | "pipes" | "simple" | "form";
export interface SdkQueryParameter extends SdkModelPropertyTypeBase {
kind: "query";
// the collection format to use to separate each value in array of a query parameter
collectionFormat?: CollectionFormat;
serializedName: string;
correspondingMethodParams: SdkModelPropertyType[];
// if true, send each value in array or object as a separate query parameter, default is `false`
explode: boolean;
}

SdkHeaderParameter

This is an HTTP header parameter.

// different collection format to use to separate each value in array
// - multi: no separator, treat each value in array as a separate header parameter
// - csv: , as separator
// - ssv: space as separator
// - tsv: tab as separator
// - pipes: | as separator
// - simple: , as separator
// - form: no separator, treat each value in array as a separate header parameter
export type CollectionFormat = "multi" | "csv" | "ssv" | "tsv" | "pipes" | "simple" | "form";
export interface SdkHeaderParameter extends SdkModelPropertyTypeBase {
kind: "header";
collectionFormat?: CollectionFormat;
serializedName: string;
correspondingMethodParams: SdkModelPropertyType[];
}

SdkPathParameter

export interface SdkPathParameter extends SdkModelPropertyTypeBase {
kind: "path";
// when interpolating this parameter in the case of array or object, expand each value using the given style, default is `false`
explode: boolean;
// different interpolating styles for the path parameter, default is `"simple"`
// - simple: no separator
// - label: . as separator
// - matrix: ; as separator
// - fragment: # as separator
// - path: / as separator
style: "simple" | "label" | "matrix" | "fragment" | "path";
// when interpolating this parameter, do not encode reserved characters, default is `false`
allowReserved: boolean;
serializedName: string;
optional: false;
correspondingMethodParams: SdkModelPropertyType[];
}

SdkBodyParameter

export interface SdkBodyParameter extends SdkModelPropertyTypeBase {
kind: "body";
optional: boolean;
contentTypes: string[];
defaultContentType: string;
correspondingMethodParams: SdkModelPropertyType[];
}

Properties

SdkBodyModelPropertyType

This represents a property on a body model

export interface SdkBodyModelPropertyType extends SdkModelPropertyTypeBase {
kind: "property";
discriminator: boolean;
serializedName: string;
isMultipartFileInput: boolean;
visibility: Visibility[];
flatten: boolean;
}

Usage

We recommend that for usage, you use one single function with switch statements for each kind of property. This allows for the circular nature of an SdkModelPropertyType.

import { SdkServiceOperation, SdkModelPropertyType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { getPythonSdkType } from "./types.js";
export function getSdkModelPropertyType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
prop: SdkModelPropertyType
): PythonSdkModelType {
const type = getPythonSdkType(context, prop.type)
switch (prop.kind) {
case "query":
case "header"
case "path":
return {
...prop,
type,
correspondingMethodParams: prop.correspondingMethodParams.map(
(x) => getSdkModelPropertyType(context, x),
)
}
default:
return {
...prop,
type,
}
}
}

SdkMethodResponse

This represents the response that our method will ultimately return. It is not tied to a transport, like http, it solely represents what we want to return to users

Interface

export interface SdkMethodResponse {
kind: "method";
// the path from the service response to what the method returns.
logicalPath?: string;
type?: SdkType;
description?: string;
details?: string;
}

Example

const sdkMethodResponse = {
kind: "method",
logicalPath: undefined,
type: {
kind: string,
},
}
const sdkPagingMethodResponse = {
kind: "method",
logicalPath: "value",
type: {
kind: "array",
valueType: {
kind: "model",
name: "Widget",
nullableValues: false,
...
}
}
}

Usage

import { SdkMethodResponse } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext, PythonSdkMethodResponse } from "./lib.js";
import { getPythonSdkType } from "./types.js";
function serializeMethodResponse<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
response: SdkMethodResponse,
): PythonSdkMethodResponse {
return {
...response,
type: getPythonSdkType(context, response.type),
};
}

SdkHttpResponse

This is the response returned from an HTTP service. The emitters should take care to map it to what the method ultimately returns to end users.

Each response is mapped to an HttpStatusCodeRange on the SdkHttpOperation interface.

Interface

interface HttpStatusCodeRange {
start: number; // inclusive start. If there's only start, then it's just a single value.
end?: number; // exclusive end. If it's a range, end is present, otherwise it's undefined.
}
export interface SdkHttpResponse {
kind: "http";
statusCodes: number | HttpStatusCodeRange | "*";
headers: SdkServiceResponseHeader[];
apiVersions: string[];
type?: SdkType;
contentTypes: string[];
defaultContentType?: string;
}

SdkType

These are the TCGC versions of each TypeSpec Type. They include information that all emitters want, i.e. their description directly on them.

They all share this base:

interface SdkTypeBase {
// the raw TypeSpec type. Some of our types are currently
// created by tcgc. Those won't have an original type
__raw?: Type;
kind: string;
deprecation?: string;
description?: string;
details?: string;
}

SdkBuiltInType

A SdkBuiltInType represents a built-in scalar TypeSpec type or scalar type that derives from a built-in scalar TypeSpec type, but datetime and duration are not included. We add encode onto these types if @encode decorator exists, telling us how to encode when sending to the service.

There is a one-to-one mapping between the TypeSpec scalar kinds and the SdkBuiltInKinds.

Interface

export interface SdkBuiltInType extends SdkTypeBase {
kind: SdkBuiltInKinds;
name: string;
encode?: string;
baseType?: SdkBuiltInType;
crossLanguageDefinitionId: string;
}

The crossLanguageDefinitionId represents the fully qualified name of this type in TypeSpec language for the emitter to distinguish from the built-in TypeSpec types.

For a full list of types defined in @azure-tools/typespec-azure-core library, please refer to its reference doc.

SdkDateTimeType

interface SdkDatetimeTypeBase extends SdkTypeBase {
name: string;
baseType?: SdkDateTimeType;
encode: DateTimeKnownEncoding;
// what we send over the wire. Often it's string
wireType: SdkBuiltInType;
crossLanguageDefinitionId: string;
}
interface SdkUtcDatetimeType extends SdkDatetimeTypeBase {
kind: "utcDateTime";
}
interface SdkOffsetDatetimeType extends SdkDatetimeTypeBase {
kind: "offsetDateTime";
}

SdkDurationType

interface SdkDurationType extends SdkTypeBase {
kind: "duration";
name: string;
baseType?: SdkDurationType;
encode: DurationKnownEncoding;
// What we send over the wire. It's usually either a string or a float
wireType: SdkBuiltInType;
crossLanguageDefinitionId: string;
}

SdkArrayType

interface SdkArrayType extends SdkTypeBase {
kind: "array";
name: string;
valueType: SdkType;
crossLanguageDefinitionId: string;
}

SdkDictionaryType

interface SdkDictionaryType extends SdkTypeBase {
kind: "dict";
keyType: SdkType; // currently can only be string
valueType: SdkType;
}

SdkEnumType

export interface SdkEnumType extends SdkTypeBase {
kind: "enum";
name: string;
// Determines whether the name was generated or not
isGeneratedName: boolean;
valueType: SdkBuiltInType;
values: SdkEnumValueType[];
isFixed: boolean;
isFlags: boolean;
usage: UsageFlags;
access: AccessFlags;
crossLanguageDefinitionId: string;
apiVersions: string[];
isUnionAsEnum: boolean;
}

SdkEnumValueType

export interface SdkEnumValueType extends SdkTypeBase {
kind: "enumvalue";
name: string;
value: string | number;
enumType: SdkEnumType;
valueType: SdkBuiltInType;
}

SdkConstantType

export interface SdkConstantType extends SdkTypeBase {
kind: "constant";
value: string | number | boolean | null;
valueType: SdkBuiltInType;
name: string;
isGeneratedName: boolean;
}

SdkUnionType

export interface SdkUnionType extends SdkTypeBase {
name: string;
// determines if the union name was generated or not
isGeneratedName: boolean;
kind: "union";
variantTypes: SdkType[];
crossLanguageDefinitionId: string;
}

SdkTupleType

export interface SdkTupleType extends SdkTypeBase {
kind: "tuple";
valueTypes: SdkType[];
}

SdkModelType

export interface SdkModelType extends SdkTypeBase {
kind: "model";
// purposely can also be header / query params for fidelity with TypeSpec
properties: SdkModelPropertyType[];
// we will always have a name. generatedName determines if it's generated or not.
name: string;
isGeneratedName: boolean;
access: AccessFlags;
usage: UsageFlags;
additionalProperties?: SdkType;
discriminatorValue?: string;
discriminatedSubtypes?: Record<string, SdkModelType>;
discriminatorProperty?: SdkModelPropertyType;
baseModel?: SdkModelType;
crossLanguageDefinitionId: string;
apiVersions: string[];
}

UsageFlags

export enum UsageFlags {
None = 0,
Input = 1 << 1,
Output = 1 << 2,
ApiVersionEnum = 1 << 3,
JsonMergePatch = 1 << 4,
MultipartFormData = 1 << 5,
}

AccessFlags

We default the value of .access property on model, enum, and method types to be "public". So if the @access decorator isn’t explicitly applied to one of these definitions, its value will be "public".

If you want to know if a tsp author explicitly set the value with an @access decorator, you can call getAccessOverride

export type AccessFlags = "internal" | "public";

SdkEndpointType

export interface SdkEndpointType {
kind: "endpoint";
serverUrl: string;
templateArguments: SdkPathParameter[];
}

Example Types

The example types help to model the examples that TypeSpec author defined to help user understand how to use the API.

We currently only have examples based on the payload, so the examples model will bind to the SdkServiceOperation.

These types are used to represent an example of a service operation.

We currently only support HTTP calls to the service.

So, we have SdkHttpoperationExample bind to SdkHttpOperation, SdkHttpParameterExampleValue bind to SdkHttpParameter, SdkHttpResponseExampleValue bind to SdkHttpResponse, and SdkHttpResponseHeaderExampleValue bind to SdkHttpResponseHeader.

Each type will have the example value type and its cooresponding definition type.

interface SdkExampleBase {
kind: string;
name: string;
description: string;
filePath: string;
rawExample: any;
}
export interface SdkHttpOperationExample extends SdkExampleBase {
kind: "http";
parameters: SdkHttpParameterExampleValue[];
responses: SdkHttpResponseExampleValue[];
}
export interface SdkHttpParameterExampleValue {
parameter: SdkHttpParameter;
value: SdkExampleValue;
}
export interface SdkHttpResponseExampleValue {
response: SdkHttpResponse;
statusCode: number;
headers: SdkHttpResponseHeaderExampleValue[];
bodyValue?: SdkExampleValue;
}
export interface SdkHttpResponseHeaderExampleValue {
header: SdkServiceResponseHeader;
value: SdkExampleValue;
}

SdkExampleValue

These types are used to represent the example value of a type. One definition types will have different example value types. For SdkUnionExampleValue, since it is hard to determine whether the example value should belong to which union variant, we will keep the raw value and leave the work for the emitter. For SdkModelExampleValue, we will help to map the example type to the right subtype for the discriminated type, and we will separate the additional properties value from the property value. But for the model with inheritance, we will not break down the type graph, just put all the example value in the child model.

export type SdkExampleValue =
| SdkStringExampleValue
| SdkNumberExampleValue
| SdkBooleanExampleValue
| SdkNullExampleValue
| SdkUnknownExampleValue
| SdkArrayExampleValue
| SdkDictionaryExampleValue
| SdkUnionExampleValue
| SdkModelExampleValue;
interface SdkExampleValueBase {
kind: string;
type: SdkType;
value: unknown;
}
export interface SdkStringExampleValue extends SdkExampleTypeBase {
kind: "string";
type:
| SdkBuiltInType
| SdkDateTimeType
| SdkDurationType
| SdkEnumType
| SdkEnumValueType
| SdkConstantType;
value: string;
}
export interface SdkNumberExampleValue extends SdkExampleTypeBase {
kind: "number";
type:
| SdkBuiltInType
| SdkDateTimeType
| SdkDurationType
| SdkEnumType
| SdkEnumValueType
| SdkConstantType;
value: number;
}
export interface SdkBooleanExampleValue extends SdkExampleTypeBase {
kind: "boolean";
type: SdkBuiltInType | SdkConstantType;
value: boolean;
}
export interface SdkNullExampleValue extends SdkExampleTypeBase {
kind: "null";
type: SdkNullableType;
value: null;
}
export interface SdkUnknownExampleValue extends SdkExampleTypeBase {
kind: "unknown";
type: SdkBuiltInType;
value: unknown;
}
export interface SdkArrayExampleValue extends SdkExampleTypeBase {
kind: "array";
type: SdkArrayType;
value: SdkExampleValue[];
}
export interface SdkDictionaryExampleValue extends SdkExampleTypeBase {
kind: "dict";
type: SdkDictionaryType;
value: Record<string, SdkExampleValue>;
}
export interface SdkUnionExampleValue extends SdkExampleTypeBase {
kind: "union";
type: SdkUnionType;
value: unknown;
}
export interface SdkModelExampleValue extends SdkExampleTypeBase {
kind: "model";
type: SdkModelType;
value: Record<string, SdkExampleValue>;
additionalPropertiesValue?: Record<string, SdkExampleValue>;
}

Example

const sdkHttpOperationExample = {
kind: "http",
name: "Example 1",
description: "Example 1",
filePath: "example1.json",
rawExample: {
operationId: "Widgets_Read",
title: "Example 1",
parameters: {
id: "test",
},
responses: {
200: {
body: {
id: "test",
color: "red",
weight: 100,
},
},
},
},
parameters: [
{
parameter: {}, // ref to the SdkHttpParameter
value: {
kind: "string",
type: {}, // ref to the SdkType
value: "test",
},
},
],
responses: {
200: {
response: {}, // ref to the SdkHttpResponse
bodyValue: {
kind: "model",
type: {}, // ref to the SdkType
value: {
id: {
kind: "string",
type: {}, // ref to the SdkType
value: "test",
},
color: {
kind: "string",
type: {}, // ref to the SdkType
value: "red",
},
weight: {
kind: "number",
type: {}, // ref to the SdkType
value: 100,
},
},
},
},
},
};

Usage

function serializeServiceOperationExample(
context: PythonSdkContext<SdkHttpOperation>,
operation: SdkHttpOperation,
): PythonSdkHttpOperationExample {
for (const example of operation.examples) {
return {
name: example.name,
filePath: example.filePath,
parameters: example.parameters.map((x) => ({
parameter: getSdkModelPropertyType(x.parameter),
value: serializeTypeExample(context, x.value),
})),
responses: new Map(
[...example.responses].map(([key, value]) => [
key,
{
response: serializeHttpResponse(value.response),
headers: value.headers.map((x) => ({
header: getSdkModelPropertyType(x.header),
value: serializeTypeExample(context, x.value),
})),
bodyValue: serializeTypeExample(context, value.bodyValue),
},
]),
),
};
}
}
function serializeTypeExample(
context: PythonSdkContext<SdkHttpOperation>,
example: SdkExampleValue,
): PythonSdkTypeExample {
switch (example.kind) {
case "string":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "number":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "boolean":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "null":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "unknown":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "array":
return {
...example,
type: getPythonSdkType(context, example.type),
value: example.value.map((x) => serializeTypeExample(context, x)),
};
case "dict":
return {
...example,
type: getPythonSdkType(context, example.type),
value: Object.fromEntries(
Object.entries(example.value).map(([key, value]) => [
key,
serializeTypeExample(context, value),
]),
),
};
case "union":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "model":
return {
...example,
type: getPythonSdkType(context, example.type),
value: Object.fromEntries(
Object.entries(example.value).map(([key, value]) => [
key,
serializeTypeExample(context, value),
]),
),
additionalPropertiesValue: example.additionalPropertiesValue
? Object.fromEntries(
Object.entries(example.additionalPropertiesValue).map(([key, value]) => [
key,
serializeTypeExample(context, value),
]),
)
: undefined,
};
}
}

Complete Usage Code

lib.ts

import { SdkContext } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkType, PythonSdkServiceOperation } from "./interfaces.js";
export interface PythonEmitterOptions {
"flavor"?: "azure";
...
}
export interface PythonSdkContext<
TServiceOperation extends PythonSdkServiceOperation
> extends SdkContext<PythonEmitterOptions, TServiceOperation> {
__endpointPathParameters: Record<str, Any>;
pythonTypesMap: Record<str, PythonSdkType>;
}

emitter.ts

import { EmitContext } from "@typespec/compiler";
import {
SdkContext,
createSdkContext,
SdkClientType,
} from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { getPythonSdkType, getSdkModelPropertyType } from "./types.js";
import {
PythonSdkType,
PythonSdkPackage,
PythonSdkClientType,
PythonSdkServiceMethod,
PythonSdkServiceOperation,
} from "./interfaces.js";
import { serializeServiceMethod } from "./methods.js";
export async function $onEmit(context: EmitContext<PythonEmitterOptions>) {
const sdkContext: PythonSdkContext = {
...createSdkContext(context, "@azure-tools/typespec-python"),
__endpointPathParameters: {},
};
const pythonSdkPackage: PythonSdkPackage = {
clients: sdkContext.clients.map((x) => serializeClient(sdkContext, x)),
rootNamespace: sdkContext.sdkPackage.rootNamespace,
models: sdkContext.sdkPackage.models.map((x) => getPythonSdkType(sdkContext, x)),
enums: sdkContext.sdkPackage.enums.map((x) => getPythonSdkType(sdkContext, x)),
};
linkSubClients(sdkContext);
// Serialize the pythonSdkPackage and pass it to your generator
}
function serializeClient<TServiceOperation extends PythonSdkServiceOperation>(
sdkContext: PythonSdkContext<TServiceOperation>,
client: SdkClientType<TServiceOperation>,
): PythonSdkClientType {
const pythonClient = {
...client,
parameters: client.initialization?.properties.map((x) =>
getSdkModelPropertyType(sdkContext, x),
),
subClients: client.methods
.filter((x) => x.kind === "clientaccessor")
.map((x) => serializeClient(sdkContext, x.response)),
methods: client.methods
.filter((x) => x.kind === "method")
.map((x) => serializeServiceMethod(sdkContext, x)),
subclients: [],
};
sdkContext, ClientMap.set(client, pythonClient);
return pythonClient;
}
function linkSubClients<TServiceOperation extends PythonSdkServiceOperation>(
sdkContext: PythonSdkContext<TServiceOperation>,
): void {
for (const client of sdkContext.clients) {
client.subclients = client.methods
.filter((x) => x.kind === "clientaccessor")
.map((x) => x.returnType)
.map((x) => sdkContext.ClientMap.get(x));
}
}

methods.ts

import {
SdkClientType,
SdkServiceMethod,
SdkBasicServiceMethod,
SdkPagingServiceMethod,
SdkLroServiceMethod,
SdkLroPagingServiceMethod,
} from "@azure-tools/typespec-client-generator-core";
import {
PythonSdkBasicServiceMethod,
PythonSdkPagingServiceMethod,
PythonSdkLroServiceMethod,
PythonSdkLroPagingServiceMethod,
PythonSdkServiceOperation,
} from "./interfaces.js";
import { PythonSdkContext } from "./lib.js";
import { serializeHttpServiceOperation } from "./http.js";
export function serializeServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
sdkContext: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>
): PythonSdkServiceMethod<TServiceOperation> {
switch (method.kind) {
case "basic":
return serializeBasicServiceMethod(context, method);
case "paging":
return serializePagingServiceMethod(context, method);
case "lro":
return serializeLroServiceMethod(context, method);
case "lropaging":
return serializeLroPagingServiceMethod(context, method);
}
}
function serializeBasicServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkBasicServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation)
},
}
function serializePagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkPagingServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}
function serializeLroServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroServiceMethod<TServiceOperation>,
): PythonSdkLroServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
},
}
function serializeLroPagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroPagingServiceMethod<TServiceOperation>,
): PythonSdkLroPagingServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}
function serializeServiceOperation<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>,
operation: TServiceOperation
): TServiceOperation {
switch (operation.kind) {
case "http": {
return serializeHttpServiceOperation(
context, operation
)
}
}
}
function serializeHttpServiceOperation(
context: PythonSdkContext<SdkHttpOperation>,
operation: SdkHttpOperation,
): PythonSdkHttpOperation {
return {
...operation,
parameters: operation.parameters.map(
(x) => getSdkModelPropertyType(context, x)
),
bodyParam: (
operation.bodyParam ?
getSdkModelPropertyType(context, operation.bodyParam) : undefined
),
responses: Object.entries(operation.responses).map(
([statusCode, responses]) => serializeHttpResponse(context, method, statusCode, responses)
),
exceptions: Object.entries(operation.exceptions).map(
([statusCode, exceptions]) => serializeHttpResponse(context, method, statusCode, exceptions)
),
}
}

http.js

import { SdkClientType, SdkHttpOperation } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { PythonSdkHttpOperation, PythonSdkHttpResponse } from "./interfaces.js";
import { getPythonSdkType, getSdkModelPropertyType } from "./types.js";
function serializeHttpServiceOperation(
context: PythonSdkContext<SdkHttpOperation>,
method: SdkServiceMethod<SdkHttpOperation>,
operation: SdkHttpOperation,
): PythonSdkHttpOperation {
return {
...operation,
parameters: operation.parameters.map((x) => getSdkModelPropertyType(context, x)),
bodyParam: operation.bodyParam
? getSdkModelPropertyType(context, operation.bodyParam)
: undefined,
responses: Object.entries(operation.responses).map(([statusCode, responses]) =>
serializeHttpResponse(context, method, statusCode, responses),
),
exceptions: Object.entries(operation.exceptions).map(([statusCode, exceptions]) =>
serializeHttpResponse(context, method, statusCode, exceptions),
),
};
function serializeHttpResponse(
context: PythonSdkContext<SdkHttpOperation>,
response: SdkHttpResponse,
): PythonSdkHttpResponse {
return {
...response,
type: response.type ? getPythonSdkType(context, response.type) : undefined,
};
}
}

types.ts

import { SdkServiceOperation, SdkType, SdkModelType, SdkModelPropertyType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { PythonSdkType, PythonSdkModelType, PythonSdkEnumType } from "./interfaces.js";
export function getSdkModelPropertyType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
prop: SdkModelPropertyType
): PythonSdkModelType {
const type = getPythonSdkType(context, prop.type)
switch (prop.kind) {
case "query":
case "header"
case "path":
return {
...prop,
correspondingMethodParams: prop.correspondingMethodParams.map(
(x) => getSdkModelPropertyType(context, x),
)
}
default:
return {
...prop,
type,
}
}
}
export function getPythonSdkType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
type: SdkType,
): PythonSdkType {
// We don't have to convert all types, just types that we want
// to add more information onto
switch (type.kind) {
case "model":
return getPythonModelType(context, type);
case "enum":
return getPythonEnumType(context, type);
default:
return type;
}
}
function getPythonModelType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
type: SdkModelType,
): PythonSdkModelType {
return {
...type,
snakeCaseName: camelToSnakeCase(type.name),
properties: type.properties.map(
(x) => getPythonBodyModelPropertyType(
context, x,
)
),
baseModel: type.baseModel ? getPythonModelType(context, type.baseModel) : undefined,
...
}
}