Skip to content

Commit d53a6f2

Browse files
Fix array docs not showing up in openapi emitter (#2004)
1 parent afd531a commit d53a6f2

10 files changed

Lines changed: 64 additions & 19 deletions

File tree

common/changes/@typespec/compiler/fix-array-assignable-to-object_2023-04-11-18-47.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
{
99
"packageName": "@typespec/compiler",
10-
"comment": "**DEPRECATION** `object` is deprecated. Alternative is to use `{}` for an empty model, `Record<unknown>` for a record with unknown propertie types, `unknown[]` for an array.",
10+
"comment": "**DEPRECATION** `object` is deprecated. Alternative is to use `{}` for an empty model, `Record<unknown>` for a record with unknown property types, `unknown[]` for an array.",
1111
"type": "none"
1212
}
1313
],
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@typespec/compiler",
5+
"comment": "Mark `Array` and `Record` doc comment as for dev only",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@typespec/compiler"
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@typespec/openapi3",
5+
"comment": "Fix: Documentation on `model is x[]` was not included in schema description",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@typespec/openapi3"
10+
}

packages/compiler/core/checker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5307,7 +5307,8 @@ function extractMainDoc(type: Type): string | undefined {
53075307
for (const doc of type.node.docs) {
53085308
mainDoc += getDocContent(doc.content);
53095309
}
5310-
return mainDoc;
5310+
const trimmed = mainDoc.trim();
5311+
return trimmed === "" ? undefined : trimmed;
53115312
}
53125313

53135314
function extractParamDoc(node: OperationStatementNode, paramName: string): string | undefined {

packages/compiler/lib/lib.tsp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,18 @@ scalar boolean;
124124
* Represent a model
125125
*/
126126
// Deprecated June 2023 sprint
127-
@deprecated("object is deprecated. Please use {} for an empty model, `Record<unknown>` for a record with unknown propertie types, `unknown[]` for an array.")
127+
@deprecated("object is deprecated. Please use {} for an empty model, `Record<unknown>` for a record with unknown property types, `unknown[]` for an array.")
128128
model object {}
129129

130130
/**
131-
* Array model type, equivalent to `T[]`
131+
* @dev Array model type, equivalent to `T[]`
132132
* @template T The type of the array elements
133133
*/
134134
@indexer(integer, T)
135135
model Array<T> {}
136136

137137
/**
138-
* Model with string properties where all the properties have type `T`
138+
* @dev Model with string properties where all the properties have type `T`
139139
* @template T The type of the properties
140140
*/
141141
@indexer(string, T)

packages/openapi3/src/openapi.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
887887

888888
if (type.kind === "String" || type.kind === "Number" || type.kind === "Boolean") {
889889
// For literal types, we just want to emit them directly as well.
890-
return mapTypeSpecTypeToOpenAPI(type, visibility);
890+
return getSchemaForLiterals(type);
891891
}
892892

893893
if (type.kind === "Intrinsic" && type.name === "unknown") {
@@ -1251,7 +1251,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
12511251
}
12521252

12531253
function getSchemaForType(type: Type, visibility: Visibility): OpenAPI3Schema | undefined {
1254-
const builtinType = mapTypeSpecTypeToOpenAPI(type, visibility);
1254+
const builtinType = getSchemaForLiterals(type);
12551255
if (builtinType !== undefined) return builtinType;
12561256

12571257
switch (type.kind) {
@@ -1364,7 +1364,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
13641364

13651365
if (isLiteralType(variant.type)) {
13661366
if (!literalVariantEnumByType[variant.type.kind]) {
1367-
const enumSchema = mapTypeSpecTypeToOpenAPI(variant.type, visibility);
1367+
const enumSchema = getSchemaForLiterals(variant.type);
13681368
literalVariantEnumByType[variant.type.kind] = enumSchema;
13691369
schemaMembers.push({ schema: enumSchema, type: null });
13701370
} else {
@@ -1502,6 +1502,13 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
15021502
}
15031503

15041504
function getSchemaForModel(model: Model, visibility: Visibility) {
1505+
const arrayOrRecord = mapTypeSpecIntrinsicModelToOpenAPI(model, visibility);
1506+
if (arrayOrRecord) {
1507+
const arrayDoc = getDoc(program, model);
1508+
arrayOrRecord.description = arrayDoc;
1509+
return arrayOrRecord;
1510+
}
1511+
15051512
let modelSchema: OpenAPI3Schema & Required<Pick<OpenAPI3Schema, "properties">> = {
15061513
type: "object",
15071514
properties: {},
@@ -1772,16 +1779,20 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
17721779

17731780
// Map an TypeSpec type to an OA schema. Returns undefined when the resulting
17741781
// OA schema is just a regular object schema.
1775-
function mapTypeSpecTypeToOpenAPI(typespecType: Type, visibility: Visibility): any {
1782+
function getSchemaForLiterals(
1783+
typespecType: NumericLiteral | StringLiteral | BooleanLiteral
1784+
): OpenAPI3Schema;
1785+
function getSchemaForLiterals(typespecType: Type): OpenAPI3Schema | undefined;
1786+
function getSchemaForLiterals(typespecType: Type): OpenAPI3Schema | undefined {
17761787
switch (typespecType.kind) {
17771788
case "Number":
17781789
return { type: "number", enum: [typespecType.value] };
17791790
case "String":
17801791
return { type: "string", enum: [typespecType.value] };
17811792
case "Boolean":
17821793
return { type: "boolean", enum: [typespecType.value] };
1783-
case "Model":
1784-
return mapTypeSpecIntrinsicModelToOpenAPI(typespecType, visibility);
1794+
default:
1795+
return undefined;
17851796
}
17861797
}
17871798

@@ -1836,7 +1847,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
18361847
function mapTypeSpecIntrinsicModelToOpenAPI(
18371848
typespecType: Model,
18381849
visibility: Visibility
1839-
): any | undefined {
1850+
): OpenAPI3Schema | undefined {
18401851
if (typespecType.indexer) {
18411852
if (isNeverType(typespecType.indexer.key)) {
18421853
} else {
@@ -1854,6 +1865,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
18541865
}
18551866
}
18561867
}
1868+
return undefined;
18571869
}
18581870

18591871
function getSchemaForScalar(scalar: Scalar): OpenAPI3Schema {

packages/openapi3/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ export type OpenAPI3Schema = Extensions & {
431431
*
432432
* @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.23
433433
*/
434-
enum?: string[];
434+
enum?: (string | number | boolean)[];
435435

436436
/** the JSON type for the schema */
437437
type?: JsonType;

packages/openapi3/test/array.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ describe("openapi3: Array", () => {
3737
});
3838
});
3939

40+
it("named array applies doc", async () => {
41+
const res = await oapiForModel(
42+
"Pet",
43+
`
44+
@doc("This is a doc for PetNames")
45+
model PetNames is string[] {}
46+
model Pet { names: PetNames };
47+
`
48+
);
49+
deepStrictEqual(res.schemas.PetNames.description, "This is a doc for PetNames");
50+
});
51+
4052
it("can specify minItems using @minItems decorator", async () => {
4153
const res = await oapiForModel(
4254
"Pet",

packages/samples/test/output/grpc-library-example/openapi.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,8 @@ components:
355355
type: array
356356
items:
357357
$ref: '#/components/schemas/Book'
358-
x-typespec-name: Book[]
359358
description: The list of books.
359+
x-typespec-name: Book[]
360360
next_page_token:
361361
type: string
362362
description: >-
@@ -396,8 +396,8 @@ components:
396396
type: array
397397
items:
398398
$ref: '#/components/schemas/Shelf'
399-
x-typespec-name: Shelf[]
400399
description: The list of shelves.
400+
x-typespec-name: Shelf[]
401401
next_page_token:
402402
type: string
403403
description: >-

packages/samples/test/output/rest/petstore/openapi.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,8 @@ components:
603603
type: array
604604
items:
605605
$ref: '#/components/schemas/Checkup'
606-
x-typespec-name: Checkup[]
607606
description: The items on this page
607+
x-typespec-name: Checkup[]
608608
nextLink:
609609
type: string
610610
format: uri
@@ -670,8 +670,8 @@ components:
670670
type: array
671671
items:
672672
$ref: '#/components/schemas/Owner'
673-
x-typespec-name: Owner[]
674673
description: The items on this page
674+
x-typespec-name: Owner[]
675675
nextLink:
676676
type: string
677677
format: uri
@@ -731,8 +731,8 @@ components:
731731
type: array
732732
items:
733733
$ref: '#/components/schemas/Pet'
734-
x-typespec-name: Pet[]
735734
description: The items on this page
735+
x-typespec-name: Pet[]
736736
nextLink:
737737
type: string
738738
format: uri
@@ -810,8 +810,8 @@ components:
810810
type: array
811811
items:
812812
$ref: '#/components/schemas/Toy'
813-
x-typespec-name: Toy[]
814813
description: The items on this page
814+
x-typespec-name: Toy[]
815815
nextLink:
816816
type: string
817817
format: uri

0 commit comments

Comments
 (0)