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 (#1368)

fix #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.