Skip to content

August 2022

This release contains breaking changes

  • Operation parameters without decorators
  • OkResponse is no longer a template
  • Route resolution changes
  • Remove Map type
  • @path may not decorate optional properties or parameters without a default value

Operation parameters without decorators

A single undecorated (not marked @query, @header, @body or @path) operation parameter will now become a property of the request body rather than have its type define the request body. This allows defining the body with multiple unannotated parameters, which can include unannotated properties that are spread into parameters. (Previously, more than one unannotated parameter was an error.)

For example, the following used to define a request body of type string, but now defines a request body that is an object with a property named body of type string.

op create(body: string): void;

To get the previous behavior, the parameter now needs to be explicitly marked with @body:

op create(@body body: string): void;

OkResponse is no longer a template

Previously, OkResponse took an argument for the body type. Now it is a simple model like the other XxxResponse types. Alone, it implies a status code of 200 with no body.

Since 200 is the default status code for non-empty bodies, you can usually replace OkResponse<T> with simply T.

op get(id: string): OkResponse<Pet>;

Can be:

op get(id: string): Pet;

In certain situations where the body type is not (necessarily) a model, you will need to use the new Body<T> type. For example.

op list(): OkResponse<Pet[]>;

Can become:

op list(): OkResponse & Body<Pet[]>;

Since 200 status code is used by default, this could also be:

op list(): Pet[];

Generic models based on OkResponse<T> may also require Body<T>. For example:

model MyResponse<T> {
...OkResponse<T>;
@header example: string;
}

Since T is not constrainted to be a model, it might be an intrinsic type, an array, or the like, the template should be changed to use Body<T>:

model MyResponse<T> {
...OkResponse;
...Body<T>;
@header example: string;
}

In general, the prior OkResponse<T> is equivalent to OkResponse & Body<T> now or, equivalently, { ...OkResponse, ...Body<T> }. In practice there are many situations where you can leave out OkResponse altogether and use plain T rather than Body<T>.

See also https://typespec.io/docs/libraries/http/#request—response-bodies

Route resolution changes

Resolving operation routes now follows the following logic:

  • if there is a service namespace specified
    • only emit the operations and interfaces under that namespace(recursively)
  • if not:
    • only emit the operations and interfaces defined at the root (DO NOT look into namespaces)

Action if applicable

  • If a TypeSpec spec used a service namespace without @serviceTitle add the @serviceTitle decorator to the service namespace, otherwise no routes will be emitted.
  • If a TypeSpec spec contains service namespaces that are not child namespaces of the service namespace, move these namespaces under the service namespace.

Cases

Operation at the root

op test(): void;

✅ Stay the same

BeforeAfter
["/"]["/"]

Operation in namespace (not service namespace)

namespace DemoService;
op test(): void;

⚠️ Output stays the same but add warning that no routes are emitted

BeforeAfter
[][]

Operation in namespace (not service namespace) with @route

namespace DemoService;
@route("/")
op test(): void;

⚠️ Now the same as previous case, no routes emitted and emit warning

BeforeAfter
["/"][]
Resolve by adding the @serviceTitle decorator

Add @serviceTitle to the namespace

@serviceTitle("DemoService")
namespace DemoService;
@route("/")
op test(): void;

Operation in service namespace

@serviceTitle("My Service")
namespace Foo;
op test(): void;

✅ Stay the same

BeforeAfter
["/"]["/"]

Operation in namespaces other than the service namespace

import "@typespec/rest";
using TypeSpec.Http;
@serviceTitle("My Service")
namespace Foo {
@route("in-service")
op test(): void;
}
namespace MyLib {
@route("my-lib")
op test(): void;
}

⚠️ Other namespace routes are not included anymore

BeforeAfter
["/in-service", "my-lib"]["/in-service"]
Resolve by making additional namespaces children of the service namespace

Make any added namespaces children of the service namespace

import "@typespec/rest";
using TypeSpec.Http;
@serviceTitle("My Service")
namespace Foo {
@route("in-service")
op test(): void;
}
namespace Foo.MyLib {
@route("my-lib")
op test(): void;
}

Remove Map type

Map type was removed. Usages of Map<string, T> can be replaced with new type Record<T>. Other usages of Map may be replaced with object.

Map using string key type

model Foo {
options: Map<string, string>;
}

Replace with Record<T>

model Foo {
options: Record<string>;
}

Map using non-string key type

model Foo {
options: Map<int32, string>;
}

Replace with object

model Foo {
options: object;
}

@path may not decorate optional properties or parameters without a default

Properties and parameters marked with the @path decorator should be required, but may be optional if they have a default value

optional path parameters

model Foo {
@path
name?: string;
}

Was a bad practice, but was allowed in previous versions. This will now throw an error diagnostic.

Resolve by making the property required

model Foo {
@path
name: string;
}

Resolve by adding a default value

model Foo {
@path
name?: string = "singleton";
}