Skip to content

Commit

Permalink
Add new option, deprecate old one and make default valuable for examp…
Browse files Browse the repository at this point in the history
…le directory (Azure#1368)

fix Azure#1173
fix Azure/typespec-azure-pr#2754

---------

Co-authored-by: Mark Cowlishaw <[email protected]>
  • Loading branch information
timotheeguerin and markcowl committed Sep 7, 2024
1 parent cf3c501 commit 6f8497b
Show file tree
Hide file tree
Showing 14 changed files with 371 additions and 210 deletions.
28 changes: 28 additions & 0 deletions .chronus/changes/new-option-examples-dir-2024-7-13-20-55-50.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: deprecation
packages:
- "@azure-tools/typespec-autorest"
---

Replace `examples-directory` with `examples-dir` which will validate an absolute path is provided

Case 1: Examples are in `examples` directory next to `tspconfig.yaml`. In this case the option can just be removed
```diff
- examples-directory: examples
```

```diff
- examples-directory: {project-root}/examples
```

Case 2: Examples are in a different directory
```diff
- examples-directory: autorest-examples
+ examples-dir: {project-root}/autorest-examples
```

```diff
- examples-directory: {project-root}/autorest-examples
+ examples-dir: {project-root}/autorest-examples
```
8 changes: 7 additions & 1 deletion docs/emitters/typespec-autorest/reference/emitter.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,17 @@ Example: azureResourceProviderFolder is provided
- `arm-folder/AzureService/preview/2020-01-01.yaml`
- `arm-folder/AzureService/preview/2020-01-01.yaml`

### `examples-dir`

**Type:** `string`

Directory where the examples are located. Default: `{project-root}/examples`.

### `examples-directory`

**Type:** `string`

Directory where the examples are located. Default: `{cwd}/examples`.
DEPRECATED. Use examples-dir instead

### `version`

Expand Down
3 changes: 0 additions & 3 deletions packages/samples/specs/misc/x-ms-examples/tspconfig.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
emit:
- "@azure-tools/typespec-autorest"
options:
"@azure-tools/typespec-autorest":
examples-directory: "{project-root}/examples"
8 changes: 7 additions & 1 deletion packages/typespec-autorest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,17 @@ Example: azureResourceProviderFolder is provided
- `arm-folder/AzureService/preview/2020-01-01.yaml`
- `arm-folder/AzureService/preview/2020-01-01.yaml`

#### `examples-dir`

**Type:** `string`

Directory where the examples are located. Default: `{project-root}/examples`.

#### `examples-directory`

**Type:** `string`

Directory where the examples are located. Default: `{cwd}/examples`.
DEPRECATED. Use examples-dir instead

#### `version`

Expand Down
6 changes: 3 additions & 3 deletions packages/typespec-autorest/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export interface Example {
/**
* `@example` - attaches example files to an operation. Multiple examples can be specified.
*
* @param {string} param pathOrUri - path or Uri to the example file.
* @param {string} param title - name or description of the example file.
* @param {string} pathOrUri - path or Uri to the example file.
* @param {string} title - name or description of the example file.
*
* `@example` can be specified on Operations.
*/
Expand Down Expand Up @@ -50,7 +50,7 @@ export function getExamples(program: Program, entity: Type): Example[] | undefin
/**
* `@useRef` - is used to replace the TypeSpec model type in emitter output with a pre-existing named OpenAPI schema such as ARM common types.
*
* @param {string} param jsonRef - path or Uri to an OpenAPI schema.
* @param {string} jsonRef - path or Uri to an OpenAPI schema.
*
* `@useRef` can be specified on Models and ModelProperty.
*/
Expand Down
20 changes: 17 additions & 3 deletions packages/typespec-autorest/src/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createTCGCContext } from "@azure-tools/typespec-client-generator-core";
import {
EmitContext,
Namespace,
NoTarget,
Program,
Service,
emitFile,
Expand All @@ -11,6 +12,7 @@ import {
interpolatePath,
listServices,
projectProgram,
reportDeprecated,
resolvePath,
} from "@typespec/compiler";
import { resolveInfo } from "@typespec/openapi";
Expand Down Expand Up @@ -70,19 +72,31 @@ export function resolveAutorestOptions(
emitterOutputDir: string,
options: AutorestEmitterOptions
): ResolvedAutorestEmitterOptions {
const resolvedOptions = { ...defaultOptions, ...options };
const resolvedOptions = {
...defaultOptions,
...options,
};
const armTypesDir = interpolatePath(
resolvedOptions["arm-types-dir"] ?? "{project-root}/../../common-types/resource-management",
{
"project-root": program.projectRoot,
"emitter-output-dir": emitterOutputDir,
}
);

if (resolvedOptions["examples-directory"]) {
reportDeprecated(
program,
`examples-directory option is deprecated use examples-dir instead or remove it if examples are located in {project-root}/examples`,
NoTarget
);
}

return {
outputFile: resolvedOptions["output-file"],
outputDir: emitterOutputDir,
azureResourceProviderFolder: resolvedOptions["azure-resource-provider-folder"],
examplesDirectory: resolvedOptions["examples-directory"],
examplesDirectory: resolvedOptions["examples-dir"] ?? resolvedOptions["examples-directory"],
version: resolvedOptions["version"],
newLine: resolvedOptions["new-line"],
omitUnreachableTypes: resolvedOptions["omit-unreachable-types"],
Expand Down Expand Up @@ -217,7 +231,7 @@ async function emitOutput(
});

// Copy examples to the output directory
if (options.examplesDirectory && result.operationExamples.length > 0) {
if (result.operationExamples.length > 0) {
const examplesPath = resolvePath(getDirectoryPath(result.outputFile), "examples");
await program.host.mkdirp(examplesPath);
for (const { examples } of result.operationExamples) {
Expand Down
18 changes: 16 additions & 2 deletions packages/typespec-autorest/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,17 @@ export interface AutorestEmitterOptions {

/**
* Directory where the examples are located.
* @default `{cwd}/examples`
* @default `{project-root}/examples`
*/
"examples-dir"?: string;

/**
* @deprecated use {@link examples-dir}
*/
"examples-directory"?: string;

version?: string;

"azure-resource-provider-folder"?: string;

/**
Expand Down Expand Up @@ -149,10 +156,17 @@ const EmitterOptionsSchema: JSONSchemaType<AutorestEmitterOptions> = {
" - `arm-folder/AzureService/preview/2020-01-01.yaml`",
].join("\n"),
},
"examples-dir": {
type: "string",
nullable: true,
description: "Directory where the examples are located. Default: `{project-root}/examples`.",
format: "absolute-path",
},
"examples-directory": {
type: "string",
nullable: true,
description: "Directory where the examples are located. Default: `{cwd}/examples`.",
deprecated: true,
description: "DEPRECATED. Use examples-dir instead",
},
version: { type: "string", nullable: true },
"azure-resource-provider-folder": { type: "string", nullable: true },
Expand Down
63 changes: 34 additions & 29 deletions packages/typespec-autorest/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export async function getOpenAPIForService(

const operationIdsWithExample = new Set<string>();

const [exampleMap, diagnostics] = await loadExamples(program.host, options, context.version);
const [exampleMap, diagnostics] = await loadExamples(program, options, context.version);
program.reportDiagnostics(diagnostics);

const httpService = ignoreDiagnostics(getHttpService(program, service.type));
Expand Down Expand Up @@ -678,14 +678,12 @@ export async function getOpenAPIForService(
);
}

if (options.examplesDirectory) {
const examples = exampleMap.get(currentEndpoint.operationId);
if (examples && currentEndpoint.operationId) {
operationIdsWithExample.add(currentEndpoint.operationId);
currentEndpoint["x-ms-examples"] = currentEndpoint["x-ms-examples"] || {};
for (const [title, example] of Object.entries(examples)) {
currentEndpoint["x-ms-examples"][title] = { $ref: `./examples/${example.relativePath}` };
}
const autoExamples = exampleMap.get(currentEndpoint.operationId);
if (autoExamples && currentEndpoint.operationId) {
operationIdsWithExample.add(currentEndpoint.operationId);
currentEndpoint["x-ms-examples"] = currentEndpoint["x-ms-examples"] || {};
for (const [title, example] of Object.entries(autoExamples)) {
currentEndpoint["x-ms-examples"][title] = { $ref: `./examples/${example.relativePath}` };
}
}

Expand Down Expand Up @@ -2563,31 +2561,38 @@ export function sortOpenAPIDocument(doc: OpenAPI2Document): OpenAPI2Document {
return sorted;
}

async function checkExamplesDirExists(host: CompilerHost, dir: string) {
try {
return (await host.stat(dir)).isDirectory();
} catch (err) {
return false;
}
}

async function loadExamples(
host: CompilerHost,
program: Program,
options: AutorestDocumentEmitterOptions,
version?: string
): Promise<[Map<string, Record<string, LoadedExample>>, readonly Diagnostic[]]> {
const host = program.host;
const diagnostics = createDiagnosticCollector();
if (!options.examplesDirectory) {
return diagnostics.wrap(new Map());
}
const exampleDir = version
? resolvePath(options.examplesDirectory, version)
: resolvePath(options.examplesDirectory);
try {
if (!(await host.stat(exampleDir)).isDirectory()) return diagnostics.wrap(new Map());
} catch (err) {
diagnostics.add(
createDiagnostic({
code: "example-loading",
messageId: "noDirectory",
format: { directory: exampleDir },
target: NoTarget,
})
);
const examplesBaseDir = options.examplesDirectory ?? resolvePath(program.projectRoot, "examples");
const exampleDir = version ? resolvePath(examplesBaseDir, version) : resolvePath(examplesBaseDir);

if (!(await checkExamplesDirExists(host, exampleDir))) {
if (options.examplesDirectory) {
diagnostics.add(
createDiagnostic({
code: "example-loading",
messageId: "noDirectory",
format: { directory: exampleDir },
target: NoTarget,
})
);
}
return diagnostics.wrap(new Map());
}

const map = new Map<string, Record<string, LoadedExample>>();
const exampleFiles = await host.readDir(exampleDir);
for (const fileName of exampleFiles) {
Expand All @@ -2600,7 +2605,7 @@ async function loadExamples(
code: "example-loading",
messageId: "noOperationId",
format: { filename: fileName },
target: NoTarget,
target: { file: exampleFile, pos: 0, end: 0 },
})
);
continue;
Expand All @@ -2615,7 +2620,7 @@ async function loadExamples(
diagnostics.add(
createDiagnostic({
code: "duplicate-example-file",
target: NoTarget,
target: { file: exampleFile, pos: 0, end: 0 },
format: {
filename: fileName,
operationId: example.operationId,
Expand Down
2 changes: 1 addition & 1 deletion packages/typespec-autorest/src/openapi2-document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export type OpenAPI2Schema = Extensions & {
* Declares the value of the property that the server will use if none is provided,
* for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request.
*
* @note "default" has no meaning for required parameters.) See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Unlike JSON Schema this value MUST conform to the defined type for this parameter. */
* "default" has no meaning for required parameters.) See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Unlike JSON Schema this value MUST conform to the defined type for this parameter. */
default?: string | boolean | number | Record<string, unknown>;

/**
Expand Down
Loading

0 comments on commit 6f8497b

Please sign in to comment.