microsoft / microsoft/typespec

[hsjs] Support or handle specifications that have multiple HTTP service declarations.

Open
#7,030 1 comment 1 reaction 0 assignees View on GitHub
bug emitter:service:js triaged:core
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

Description

### Describe the bug

I'm trying to update my tsp file (actually the todo in Allen's repo). I compiled and get this error.

```
> todo@1.0.0 tsp:compile
> tsp compile ./spec --config ./spec/tspconfig.yaml

TypeSpec compiler v1.0.0-rc.0

✔ Compiling
✔ @typespec/openapi3 generated-spec/openapi3/
✔ @typespec/http-server-csharp generated-dotnet-api-server/
⚠ @typespec/http-client-csharp generated-dotnet-api-client/
⚠ @typespec/http-client-js generated-js-api-client/
× Running @typespec/http-server-js...
Emitter "@typespec/http-server-js" crashed! This is a bug.
Please file an issue at https://github.com/microsoft/typespec/issues

Error: Unimplemented: multiple service definitions per program.
at createInitialContext (file:///workspaces/dina-dotnet/node_modules/@typespec/http-server-js/dist/src/ctx.js:42:15)
at Object.$onEmit [as emitFunction] (file:///workspaces/dina-dotnet/node_modules/@typespec/http-server-js/dist/src/index.js:13:25)
at runEmitter (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:668:23)
at file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:647:15
at trackAction (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/logger/console-sink.js:134:30)
at Object.trackAction (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/logger/console-sink.js:25:50)
at Object.trackAction (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/logger/logger.js:23:27)
at emit (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:646:18)
at compile (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/program.js:48:15)
at async compileOnce (file:///workspaces/dina-dotnet/node_modules/@typespec/compiler/dist/src/core/cli/actions/compile/compile.js:39:25)

--------------------------------------------------
Library Version 0.58.0-alpha.12
TypeSpec Compiler Version 1.0.0-rc.0
--------------------------------------------------
npm error Lifecycle script `tsp:compile` failed with error:
npm error code 1
npm error path /workspaces/dina-dotnet/packages/todo
npm error workspace todo@1.0.0
npm error location /workspaces/dina-dotnet/packages/todo
npm error command failed
npm error command sh -c tsp compile ./spec --config ./spec/tspconfig.yaml

```

### Reproduction

Main.tsp

```
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi";
import "@typespec/json-schema";
using Http;
using JsonSchema;

@service(#{
title: "Todo App",
})
@useAuth(BearerAuth)
@jsonSchema
namespace Todo;

@jsonSchema
model User {
/** An autogenerated unique id for the user */
@key
@visibility(Lifecycle.Read)
id: safeint;

/** The user's username */
@minLength(2)
@maxLength(50)
username: string;

/** The user's email address */
// @format("email") - crashes emitters for now
email: string;

/**
* The user's password, provided when creating a user
* but is otherwise not visible (and hashed by the backend)
*/
@visibility(Lifecycle.Create)
password: string;

/** Whether the user is validated. Never visible to the API. */
@invisible(Lifecycle) validated: boolean;
}

@jsonSchema
model TodoItem {
/** The item's unique id */
@visibility(Lifecycle.Read) @key id: safeint;

/** The item's title */
@maxLength(255)
title: string;

/** User that created the todo */
@visibility(Lifecycle.Read) createdBy: User.id;

/** User that the todo is assigned to */
assignedTo?: User.id;

/** A longer description of the todo item in markdown format */
description?: string;

/** The status of the todo item */
status: "NotStarted" | "InProgress" | "Completed";

/** When the todo item was created. */
@visibility(Lifecycle.Read) createdAt: utcDateTime;

/** When the todo item was last updated */
@visibility(Lifecycle.Read) updatedAt: utcDateTime;

/** When the todo item was makred as completed */
@visibility(Lifecycle.Read) completedAt?: utcDateTime;

// Want the read form to be normalized to TodoLabelRecord[], but can't
// https://github.com/microsoft/typespec/issues/2926
labels?: TodoLabels;

// hack to get a different schema for create
// (fastify glue doesn't support readonly)
@visibility(Lifecycle.Create) _dummy?: string;
}

model ToDoItemMultipartRequest {
item: HttpPart;
attachments?: HttpPart[];
}

model FileAttachmentMultipartRequest {
contents: HttpPart;
}

@jsonSchema
union TodoLabels {
string,
string[],
TodoLabelRecord,
TodoLabelRecord[],
}

@jsonSchema
model TodoLabelRecord {
name: string;

@pattern("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")
color?: string;
}

@jsonSchema
model TodoAttachment {
/** The file name of the attachment */
@maxLength(255)
filename: string;

/** The media type of the attachment */
mediaType: string;

/** The contents of the file */
contents: bytes;
}

@jsonSchema
@error
model ApiError {
/** A machine readable error code */
code: string;

/** A human readable message */
// https://github.com/microsoft/OpenAPI/blob/main/extensions/x-ms-primary-error-message.md
@OpenAPI.extension("x-ms-primary-error-message", true)
message: string;
}

/**
* Something is wrong with you.
*/
model Standard4XXResponse extends ApiError {
@minValue(400)
@maxValue(499)
@statusCode
statusCode: int32;
}

/**
* Something is wrong with me.
*/
model Standard5XXResponse extends ApiError {
@minValue(500)
@maxValue(599)
@statusCode
statusCode: int32;
}

alias WithStandardErrors = T | Standard4XXResponse | Standard5XXResponse;

@useAuth(NoAuth)
@service(#{ title: "Todo API" })
@server("https://api.todo.com", "Production server")
namespace Users {
// would prefer to extend
// https://github.com/microsoft/typespec/issues/2922

model UserCreatedResponse {
...User;
...OkResponse;

/** The token to use to construct the validate email address url */
token: string;
}

/** The user already exists */
model UserExistsResponse extends ApiError {
...ConflictResponse;
code: "user-exists";
}

/** The user is invalid (e.g. forgot to enter email address) */
model InvalidUserResponse extends ApiError {
@statusCode statusCode: 422;
code: "invalid-user";
}

@route("/users")
@post
op create(
@body user: User,
): WithStandardErrors;
}

@route("items")
namespace TodoItems {
model PaginationControls {
/** The limit to the number of items */
@query limit?: int32 = 50;

/** The offset to start paginating at */
@query offset?: int32 = 0;
}

model TodoPage {
/** The items in the page */
@pageItems items: TodoItem[];

/** The number of items returned in this page */
pageSize: int32;

/** The total number of items */
totalSize: int32;

...PaginationControls;

/** A link to the previous page, if it exists */
@prevLink
prevLink?: url;

/** A link to the next page, if it exists */
@nextLink
nextLink?: url;
}

// deeply annoying that I have to copy/paste this...
model TodoItemPatch {
/** The item's title */
title?: TodoItem.title;

/** User that the todo is assigned to */
assignedTo?: TodoItem.assignedTo | null;

/** A longer description of the todo item in markdown format */
description?: TodoItem.description | null;

/** The status of the todo item */
status?: "NotStarted" | "InProgress" | "Completed";
}

model InvalidTodoItem extends ApiError {
@statusCode statusCode: 422;
}

@error
model NotFoundErrorResponse {
@statusCode statusCode: 404;
code: "not-found";
}

//@friendlyName("{name}List", T)
model Page {
@pageItems items: T[];
}

@list op list(...PaginationControls): WithStandardErrors;

@sharedRoute
@post
op createJson(
@header contentType: "application/json",
item: TodoItem,
attachments?: TodoAttachment[],
): WithStandardErrors;

@sharedRoute
@post
op createForm(
@header contentType: "multipart/form-data",
@multipartBody body: ToDoItemMultipartRequest,
): WithStandardErrors;

@get op get(@path id: TodoItem.id): TodoItem | NotFoundErrorResponse;
@patch op update(
@header contentType: "application/merge-patch+json",
@path id: TodoItem.id,
@body patch: TodoItemPatch,
): TodoItem;
@delete op delete(
@path id: TodoItem.id,
): WithStandardErrors;

@route("{itemId}/attachments")
namespace Attachments {
@list op list(
@path itemId: TodoItem.id,
): WithStandardErrors | NotFoundErrorResponse>;

@sharedRoute
@post
op createJsonAttachment(
@header contentType: "application/json",
@path itemId: TodoItem.id,
@body contents: TodoAttachment,
): WithStandardErrors;

@sharedRoute
@post
op createFileAttachment(
@header contentType: "multipart/form-data",
@path itemId: TodoItem.id,
@multipartBody body: FileAttachmentMultipartRequest,
): WithStandardErrors;
}
}
```

tspconfig.yaml

```
emit:
- "@typespec/openapi3"
- "@typespec/http-server-csharp"
- "@typespec/http-client-csharp"
- "@typespec/http-client-js"
- "@typespec/http-server-js"
options:
"@typespec/openapi3":
emitter-output-dir: "{project-root}/../generated-spec/openapi3"
"@typespec/http-client-csharp":
emitter-output-dir: "{project-root}/../generated-dotnet-api-client"
"@typespec/http-server-csharp":
emitter-output-dir: "{project-root}/../generated-dotnet-api-server"
"@typespec/http-client-js":
emitter-output-dir: "{project-root}/../generated-js-api-client"
"@typespec/http-server-js":
emitter-output-dir: "{project-root}/../generated-js-api-server"
```

![Image](https://github.com/user-attachments/assets/753d38d8-2b27-429d-a6fa-b3c382d27f67)

### Checklist

- [x] Follow our [Code of Conduct](https://github.com/microsoft/typespec/blob/main/CODE_OF_CONDUCT.md)
- [x] Check that there isn't already an issue that request the same bug to avoid creating a duplicate.
- [x] Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/Microsoft/typespec/discussions).
- [x] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.