Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[http] Add Parameter Decorator @cookie to Specify Cookie Parameters #4761

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions docs/libraries/http/reference/data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,21 @@ model TypeSpec.Http.ConflictResponse
| ---------- | ----- | ---------------- |
| statusCode | `409` | The status code. |

### `CookieOptions` {#TypeSpec.Http.CookieOptions}

Cookie Options.

```typespec
model TypeSpec.Http.CookieOptions
```

#### Properties

| Name | Type | Description |
| -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name? | `string` | Name in the cookie. |
| explode? | `boolean` | If false the value will be joined with `,`.<br /><br />\| Style \| Explode \| Uri Template \| Primitive value id = 5 \| Array id = [3, 4, 5] \| Object id = {"role": "admin", "firstName": "Alex"} \|<br />\| ----- \| ------- \| -------------\| ---------------------- \| -------------------- \| -------------------------------------------------- \|<br />\| form \| true \| ` ` \| `Cookie: id=5` \| \| \|<br />\| form \| false \| `id={id}` \| `Cookie: id=5` \| `Cookie: id=3,4,5 `\| `Cookie: id=role,admin,firstName,Alex` \| |

### `CreatedResponse` {#TypeSpec.Http.CreatedResponse}

The request has succeeded and a new resource has been created as a result.
Expand Down
41 changes: 41 additions & 0 deletions docs/libraries/http/reference/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,47 @@ op download(): {
};
```

### `@cookie` {#@TypeSpec.Http.cookie}

Specify this property is to be sent or received in the cookie.

```typespec
@TypeSpec.Http.cookie(cookieNameOrOptions?: valueof string | TypeSpec.Http.CookieOptions)
```

#### Target

`ModelProperty`

#### Parameters

| Name | Type | Description |
| ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| cookieNameOrOptions | `valueof string \| TypeSpec.Http.CookieOptions` | Optional name of the cookie in the cookie or cookie options.<br />By default the cookie name will be the property name converted from camelCase to snake_case. (e.g. `authToken` -> `auth_token`) |

#### Examples

```typespec
op read(@cookie token: string): {
data: string[];
};
op create(
@cookie({
name: "auth_token",
})
data: string[],
): void;
```

##### Implicit header name

```typespec
op read(): {
@cookie authToken: string;
}; // headerName: auth_token
op update(@cookie AuthToken: string): void; // headerName: auth_token
```

### `@delete` {#@TypeSpec.Http.delete}

Specify the HTTP verb for the target operation to be `DELETE`.
Expand Down
2 changes: 2 additions & 0 deletions docs/libraries/http/reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ npm install --save-peer @typespec/http
- [`@body`](./decorators.md#@TypeSpec.Http.body)
- [`@bodyIgnore`](./decorators.md#@TypeSpec.Http.bodyIgnore)
- [`@bodyRoot`](./decorators.md#@TypeSpec.Http.bodyRoot)
- [`@cookie`](./decorators.md#@TypeSpec.Http.cookie)
- [`@delete`](./decorators.md#@TypeSpec.Http.delete)
- [`@get`](./decorators.md#@TypeSpec.Http.get)
- [`@head`](./decorators.md#@TypeSpec.Http.head)
Expand Down Expand Up @@ -64,6 +65,7 @@ npm install --save-peer @typespec/http
- [`Body`](./data-types.md#TypeSpec.Http.Body)
- [`ClientCredentialsFlow`](./data-types.md#TypeSpec.Http.ClientCredentialsFlow)
- [`ConflictResponse`](./data-types.md#TypeSpec.Http.ConflictResponse)
- [`CookieOptions`](./data-types.md#TypeSpec.Http.CookieOptions)
- [`CreatedResponse`](./data-types.md#TypeSpec.Http.CreatedResponse)
- [`File`](./data-types.md#TypeSpec.Http.File)
- [`ForbiddenResponse`](./data-types.md#TypeSpec.Http.ForbiddenResponse)
Expand Down
42 changes: 42 additions & 0 deletions packages/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Available ruleSets:
- [`@body`](#@body)
- [`@bodyIgnore`](#@bodyignore)
- [`@bodyRoot`](#@bodyroot)
- [`@cookie`](#@cookie)
- [`@delete`](#@delete)
- [`@get`](#@get)
- [`@head`](#@head)
Expand Down Expand Up @@ -145,6 +146,47 @@ op download(): {
};
```

#### `@cookie`

Specify this property is to be sent or received in the cookie.

```typespec
@TypeSpec.Http.cookie(cookieNameOrOptions?: valueof string | TypeSpec.Http.CookieOptions)
```

##### Target

`ModelProperty`

##### Parameters

| Name | Type | Description |
| ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| cookieNameOrOptions | `valueof string \| TypeSpec.Http.CookieOptions` | Optional name of the cookie in the cookie or cookie options.<br />By default the cookie name will be the property name converted from camelCase to snake_case. (e.g. `authToken` -> `auth_token`) |

##### Examples

```typespec
op read(@cookie token: string): {
data: string[];
};
op create(
@cookie({
name: "auth_token",
})
data: string[],
): void;
```

###### Implicit header name

```typespec
op read(): {
@cookie authToken: string;
}; // headerName: auth_token
op update(@cookie AuthToken: string): void; // headerName: auth_token
```

#### `@delete`

Specify the HTTP verb for the target operation to be `DELETE`.
Expand Down
28 changes: 28 additions & 0 deletions packages/http/generated-defs/TypeSpec.Http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import type {
Type,
} from "@typespec/compiler";

export interface CookieOptions {
readonly name?: string;
}

export interface QueryOptions {
readonly name?: string;
readonly explode?: boolean;
Expand Down Expand Up @@ -73,6 +77,29 @@ export type HeaderDecorator = (
headerNameOrOptions?: Type,
) => void;

/**
* Specify this property is to be sent or received in the cookie.
*
* @param cookieNameOrOptions Optional name of the cookie in the cookie or cookie options.
* By default the cookie name will be the property name converted from camelCase to snake_case. (e.g. `authToken` -> `auth_token`)
* @example
* ```typespec
* op read(@cookie token: string): {data: string[]};
* op create(@cookie({name: "auth_token"}) data: string[]): void;
* ```
* @example Implicit header name
*
* ```typespec
* op read(): {@cookie authToken: string}; // headerName: auth_token
* op update(@cookie AuthToken: string): void; // headerName: auth_token
* ```
*/
export type CookieDecorator = (
context: DecoratorContext,
target: ModelProperty,
cookieNameOrOptions?: string | CookieOptions,
) => void;

/**
* Specify this property is to be sent as a query parameter.
*
Expand Down Expand Up @@ -321,6 +348,7 @@ export type TypeSpecHttpDecorators = {
statusCode: StatusCodeDecorator;
body: BodyDecorator;
header: HeaderDecorator;
cookie: CookieDecorator;
query: QueryDecorator;
path: PathDecorator;
bodyRoot: BodyRootDecorator;
Expand Down
32 changes: 32 additions & 0 deletions packages/http/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,38 @@ model HeaderOptions {
*/
extern dec header(target: ModelProperty, headerNameOrOptions?: string | HeaderOptions);

/**
* Cookie Options.
*/
model CookieOptions {
/**
* Name in the cookie.
*/
name?: string;
}

/**
* Specify this property is to be sent or received in the cookie.
*
* @param cookieNameOrOptions Optional name of the cookie in the cookie or cookie options.
* By default the cookie name will be the property name converted from camelCase to snake_case. (e.g. `authToken` -> `auth_token`)
*
* @example
*
* ```typespec
* op read(@cookie token: string): {data: string[]};
* op create(@cookie({name: "auth_token"}) data: string[]): void;
* ```
*
* @example Implicit header name
*
* ```typespec
* op read(): {@cookie authToken: string}; // headerName: auth_token
* op update(@cookie AuthToken: string): void; // headerName: auth_token
* ```
*/
extern dec cookie(target: ModelProperty, cookieNameOrOptions?: valueof string | CookieOptions);

/**
* Query parameter options.
*/
Expand Down
29 changes: 29 additions & 0 deletions packages/http/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
BodyDecorator,
BodyIgnoreDecorator,
BodyRootDecorator,
CookieDecorator,
CookieOptions,
DeleteDecorator,
GetDecorator,
HeadDecorator,
Expand All @@ -49,6 +51,7 @@ import { getStatusCodesFromType } from "./status-codes.js";
import {
Authentication,
AuthenticationOption,
CookieParameterOptions,
HeaderFieldOptions,
HttpAuth,
HttpStatusCodeRange,
Expand Down Expand Up @@ -122,6 +125,32 @@ export function isHeader(program: Program, entity: Type) {
return program.stateMap(HttpStateKeys.header).has(entity);
}

/** {@inheritDoc CookieDecorator } */
export const $cookie: CookieDecorator = (
su8ru marked this conversation as resolved.
Show resolved Hide resolved
context: DecoratorContext,
entity: ModelProperty,
cookieNameOrOptions?: string | CookieOptions,
) => {
const paramName =
typeof cookieNameOrOptions === "string"
? cookieNameOrOptions
: (cookieNameOrOptions?.name ??
entity.name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase());
const options: CookieParameterOptions = {
type: "cookie",
name: paramName,
};
context.program.stateMap(HttpStateKeys.cookie).set(entity, options);
};

export function getCookieParamOptions(program: Program, entity: Type): QueryParameterOptions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be awesome if you could add some js doc comment to those new functions, we are in the process of documenting all our api

return program.stateMap(HttpStateKeys.cookie).get(entity);
}

export function isCookieParam(program: Program, entity: Type) {
return program.stateMap(HttpStateKeys.cookie).has(entity);
}

export const $query: QueryDecorator = (
context: DecoratorContext,
entity: ModelProperty,
Expand Down
17 changes: 16 additions & 1 deletion packages/http/src/http-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type Program,
} from "@typespec/compiler";
import {
getCookieParamOptions,
getHeaderFieldOptions,
getPathParamOptions,
getQueryParamOptions,
Expand All @@ -20,10 +21,16 @@ import {
} from "./decorators.js";
import { createDiagnostic } from "./lib.js";
import { Visibility, isVisible } from "./metadata.js";
import { HeaderFieldOptions, PathParameterOptions, QueryParameterOptions } from "./types.js";
import {
CookieParameterOptions,
HeaderFieldOptions,
PathParameterOptions,
QueryParameterOptions,
} from "./types.js";

export type HttpProperty =
| HeaderProperty
| CookieProperty
| ContentTypeProperty
| QueryProperty
| PathProperty
Expand All @@ -44,6 +51,11 @@ export interface HeaderProperty extends HttpPropertyBase {
readonly options: HeaderFieldOptions;
}

export interface CookieProperty extends HttpPropertyBase {
readonly kind: "cookie";
readonly options: CookieParameterOptions;
}

export interface ContentTypeProperty extends HttpPropertyBase {
readonly kind: "contentType";
}
Expand Down Expand Up @@ -96,6 +108,7 @@ function getHttpProperty(

const annotations = {
header: getHeaderFieldOptions(program, property),
cookie: getCookieParamOptions(program, property),
query: getQueryParamOptions(program, property),
path: getPathParamOptions(program, property),
body: isBody(program, property),
Expand Down Expand Up @@ -174,6 +187,8 @@ function getHttpProperty(
} else {
return createResult({ kind: "header", options: annotations.header });
}
} else if (annotations.cookie) {
return createResult({ kind: "cookie", options: annotations.cookie });
} else if (annotations.query) {
return createResult({ kind: "query", options: annotations.query });
} else if (annotations.path) {
Expand Down
1 change: 1 addition & 0 deletions packages/http/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export const $lib = createTypeSpecLibrary({
state: {
authentication: { description: "State for the @auth decorator" },
header: { description: "State for the @header decorator" },
cookie: { description: "State for the @cookie decorator" },
query: { description: "State for the @query decorator" },
path: { description: "State for the @path decorator" },
body: { description: "State for the @body decorator" },
Expand Down
4 changes: 3 additions & 1 deletion packages/http/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isBody,
isBodyIgnore,
isBodyRoot,
isCookieParam,
isHeader,
isMultipartBodyProperty,
isPathParam,
Expand Down Expand Up @@ -219,11 +220,12 @@ export function resolveRequestVisibility(

/**
* Determines if a property is metadata. A property is defined to be
* metadata if it is marked `@header`, `@query`, `@path`, or `@statusCode`.
* metadata if it is marked `@header`, `@cookie`, `@query`, `@path`, or `@statusCode`.
*/
export function isMetadata(program: Program, property: ModelProperty) {
return (
isHeader(program, property) ||
isCookieParam(program, property) ||
isQueryParam(program, property) ||
isPathParam(program, property) ||
isStatusCode(program, property)
Expand Down
1 change: 1 addition & 0 deletions packages/http/src/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ function getOperationParametersForVerb(
}
// eslint-disable-next-line no-fallthrough
case "query":
case "cookie":
case "header":
parameters.push({
...item.options,
Expand Down
Loading
Loading