Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .chronus/changes/migrate-valueof-remaining-2025-1-21-17-35-12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@typespec/compiler"
---

Migrate `@service` decorator options to take in a value

```diff lang="tsp"
-@service({title: "My service"})
+@service(#{title: "My service"})
```
26 changes: 26 additions & 0 deletions .chronus/changes/migrate-valueof-remaining-2025-1-21-17-48-47.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@typespec/openapi"
---

Migrate `@info` decorator to expect a value

```diff lang="tsp"
-@info({ version: "1.0.0" })
+@info(#{ version: "1.0.0" })
```

```diff lang="tsp"
-@info({
+@info(#{
termsOfService: "http://example.com/terms/",
- contact: {
+ contact: #{
name: "API Support",
url: "http://www.example.com/support",
email: "support@example.com"
},
})
```
13 changes: 13 additions & 0 deletions .chronus/changes/migrate-valueof-remaining-2025-1-21-17-57-6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: internal
packages:
- "@typespec/http-server-csharp"
- "@typespec/http-specs"
- "@typespec/http"
- "@typespec/openapi3"
- "@typespec/rest"
- "@typespec/versioning"
---

Update tests and samples
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ using TypeSpec.Http;
using TypeSpec.Rest;

/** This is a pet store service. */
@service({
title: "Pet Store Service",
})
@service(#{ title: "Pet Store Service" })
@server("https://example.com", "The service endpoint")
namespace PetStore;

Expand Down
11 changes: 8 additions & 3 deletions packages/compiler/generated-defs/TypeSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import type {
UnionVariant,
} from "../src/core/index.js";

export interface ServiceOptions {
readonly title?: string;
readonly version?: string;
}

export interface ExampleOptions {
readonly title?: string;
readonly description?: string;
Expand Down Expand Up @@ -216,19 +221,19 @@ export type DeprecatedDecorator = (
* ```
* @example Setting service title
* ```typespec
* @service({title: "Pet store"})
* @service(#{title: "Pet store"})
* namespace PetStore;
* ```
* @example Setting service version
* ```typespec
* @service({version: "1.0"})
* @service(#{version: "1.0"})
* namespace PetStore;
* ```
*/
export type ServiceDecorator = (
context: DecoratorContext,
target: Namespace,
options?: Type,
options?: ServiceOptions,
) => void;

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/compiler/lib/std/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,17 @@ model ServiceOptions {
*
* @example Setting service title
* ```typespec
* @service({title: "Pet store"})
* @service(#{title: "Pet store"})
* namespace PetStore;
* ```
*
* @example Setting service version
* ```typespec
* @service({version: "1.0"})
* @service(#{version: "1.0"})
* namespace PetStore;
* ```
*/
extern dec service(target: Namespace, options?: ServiceOptions);
extern dec service(target: Namespace, options?: valueof ServiceOptions);

/**
* Specify that this model is an error type. Operations return error types when the operation has failed.
Expand Down
46 changes: 6 additions & 40 deletions packages/compiler/src/lib/service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ServiceDecorator } from "../../generated-defs/TypeSpec.js";
import type { ServiceDecorator, ServiceOptions } from "../../generated-defs/TypeSpec.js";
import { validateDecoratorUniqueOnNode } from "../core/decorator-utils.js";
import { Type, getTypeName, reportDeprecated } from "../core/index.js";
import { reportDiagnostic } from "../core/messages.js";
import { reportDeprecated } from "../core/index.js";
import type { Program } from "../core/program.js";
import { DecoratorContext, Namespace } from "../core/types.js";
import { Realm } from "../experimental/realm.js";
Expand Down Expand Up @@ -69,50 +68,17 @@ export function addService(
export const $service: ServiceDecorator = (
context: DecoratorContext,
target: Namespace,
options?: Type,
options?: ServiceOptions,
) => {
validateDecoratorUniqueOnNode(context, target, $service);

if (options && options.kind !== "Model") {
reportDiagnostic(context.program, {
code: "invalid-argument",
format: { value: options.kind, expected: "Model" },
target: context.getArgumentTarget(0)!,
});
return;
}
const serviceDetails: ServiceDetails = {};
const title = options?.properties.get("title")?.type;
const versionProp = options?.properties.get("version");
if (title) {
if (title.kind === "String") {
serviceDetails.title = title.value;
} else {
reportDiagnostic(context.program, {
code: "unassignable",
format: { sourceType: getTypeName(title), targetType: "String" },
target: context.getArgumentTarget(0)!,
});
}
}
if (versionProp) {
const version = versionProp.type;
if (options?.version) {
reportDeprecated(
context.program,
"version: property is deprecated in @service. If wanting to describe a service versioning you can use the `@typespec/versioning` library. If wanting to describe the project version you can use the package.json version.",
versionProp,
context.getArgumentTarget(0)!,
);
if (version.kind === "String") {
// eslint-disable-next-line @typescript-eslint/no-deprecated
serviceDetails.version = version.value;
} else {
reportDiagnostic(context.program, {
code: "unassignable",
format: { sourceType: getTypeName(version), targetType: "String" },
target: context.getArgumentTarget(0)!,
});
}
}

addService(context.program, target, serviceDetails);
addService(context.program, target, options);
};
4 changes: 1 addition & 3 deletions packages/compiler/templates/rest/main.tsp
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import "@typespec/http";

using TypeSpec.Http;
@service({
title: "Widget Service",
})
@service(#{ title: "Widget Service" })
namespace DemoService;

model Widget {
Expand Down
10 changes: 5 additions & 5 deletions packages/compiler/test/decorators/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("compiler: service", () => {

it("customize service title", async () => {
const { S } = await runner.compile(`
@test @service({title: "My Service"}) namespace S {}
@test @service(#{title: "My Service"}) namespace S {}

`);

Expand All @@ -59,7 +59,7 @@ describe("compiler: service", () => {

it("emit diagnostic if service title is not a string", async () => {
const diagnostics = await runner.diagnose(`
@test @service({title: 123}) namespace S {}
@test @service(#{title: 123}) namespace S {}
`);

expectDiagnostics(diagnostics, {
Expand All @@ -71,8 +71,8 @@ describe("compiler: service", () => {

it("customize service version", async () => {
const { S } = await runner.compile(`
@test @service({
#suppress "deprecated" "test"
#suppress "deprecated" "test"
@test @service(#{
version: "1.2.3"
}) namespace S {}

Expand All @@ -94,7 +94,7 @@ describe("compiler: service", () => {

it("emit diagnostic if service version is not a string", async () => {
const diagnostics = await runner.diagnose(`
@test @service({
@test @service(#{
#suppress "deprecated" "test"
version: 123
}) namespace S {}
Expand Down
18 changes: 9 additions & 9 deletions packages/compiler/test/server/get-hover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ describe("compiler: server: on hover", () => {
it("model in namespace", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

model Ani┆mal{
Expand All @@ -355,7 +355,7 @@ describe("compiler: server: on hover", () => {
it("model with one template arg", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

model Ani┆mal<T>{
Expand All @@ -376,7 +376,7 @@ describe("compiler: server: on hover", () => {
it("model with two template args", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

model Ani┆mal<T, P>{
Expand Down Expand Up @@ -433,7 +433,7 @@ describe("compiler: server: on hover", () => {
it("interface in namespace", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

interface IAct┆ions{
Expand Down Expand Up @@ -483,7 +483,7 @@ describe("compiler: server: on hover", () => {
it("operation in namespace", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

op Ea┆t(food: string): void;
Expand All @@ -500,7 +500,7 @@ describe("compiler: server: on hover", () => {
it("operation with one template arg", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

op Ea┆t<T>(food: string): void;
Expand All @@ -517,7 +517,7 @@ describe("compiler: server: on hover", () => {
it("operation with two template args", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

op Ea┆t<T, P>(food: string): void;
Expand All @@ -534,7 +534,7 @@ describe("compiler: server: on hover", () => {
it("operation in interface", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

interface IActions {
Expand All @@ -553,7 +553,7 @@ describe("compiler: server: on hover", () => {
it("operation in interface with template", async () => {
const hover = await getHoverAtCursor(
`
@service({title: "RT"})
@service(#{title: "RT"})
namespace TestNs;

interface IActions<Q> {
Expand Down
8 changes: 4 additions & 4 deletions packages/http-client/test/typekit/client-library.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ beforeEach(async () => {
describe("listNamespaces", () => {
it("basic", async () => {
await runner.compile(`
@service({
@service(#{
title: "Widget Service",
})
namespace DemoService;
Expand All @@ -26,7 +26,7 @@ describe("listNamespaces", () => {
it("nested", async () => {
// we only want to return the top level namespaces
await runner.compile(`
@service({
@service(#{
title: "Widget Service",
})
namespace DemoService {
Expand Down Expand Up @@ -65,7 +65,7 @@ describe("listClients", () => {

it("should get the client", async () => {
const { DemoService } = (await runner.compile(`
@service({
@service(#{
title: "Widget Service",
})
@test namespace DemoService;
Expand All @@ -77,7 +77,7 @@ describe("listClients", () => {
});
it("get subclients", async () => {
const { DemoService } = (await runner.compile(`
@service({
@service(#{
title: "Widget Service",
})
@test namespace DemoService {
Expand Down
Loading