Skip to content

Commit

Permalink
Fix: @typespec/openapi3/invalid-component-fixed-field-key show on inc…
Browse files Browse the repository at this point in the history
…orrect target (#5901)

Fix: #5832
Result:

![image](https://github.com/user-attachments/assets/bbd14661-1a82-4fca-a1ea-dcb4c9bdf239)

---------

Co-authored-by: albertxavier100 <[email protected]>
  • Loading branch information
wanlwanl and albertxavier100 authored Feb 26, 2025
1 parent c79e263 commit b742e16
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 26 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/wanl-fix-diag-1-2025-1-7-19-3-44.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/openapi3"
---

Fix: `@typespec/openapi3/invalid-component-fixed-field-key` show on incorrect target
2 changes: 1 addition & 1 deletion packages/openapi3/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export const libDef = {
},
},
"invalid-component-fixed-field-key": {
severity: "error",
severity: "warning",
messages: {
default: paramMessage`Invalid key '${"value"}' used in a fixed field of the Component object. Only alphanumerics, dot (.), hyphen (-), and underscore (_) characters are allowed in keys.`,
},
Expand Down
28 changes: 4 additions & 24 deletions packages/openapi3/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import {
unsafe_mutateSubgraphWithNamespace,
unsafe_MutatorWithNamespace,
} from "@typespec/compiler/experimental";
import {} from "@typespec/compiler/utils";
import {
AuthenticationOptionReference,
AuthenticationReference,
Expand Down Expand Up @@ -117,6 +116,7 @@ import {
} from "./types.js";
import {
deepEquals,
ensureValidComponentFixedFieldKey,
getDefaultValue,
isBytesKeptRaw,
isSharedHttpOperation,
Expand Down Expand Up @@ -681,7 +681,6 @@ function createOAPIEmitter(
}
}
}

return [root, diagnostics.diagnostics];
} catch (err) {
if (err instanceof ErrorTypeFoundError) {
Expand Down Expand Up @@ -1567,21 +1566,6 @@ function createOAPIEmitter(
}
}

function validateComponentFixedFieldKey(type: Type, name: string) {
const pattern = /^[a-zA-Z0-9.\-_]+$/;
if (!pattern.test(name)) {
program.reportDiagnostic(
createDiagnostic({
code: "invalid-component-fixed-field-key",
format: {
value: name,
},
target: type,
}),
);
}
}

function emitParameters() {
for (const [property, param] of params) {
const key = getParameterKey(
Expand All @@ -1591,14 +1575,12 @@ function createOAPIEmitter(
root.components!.parameters!,
typeNameOptions,
);
validateComponentFixedFieldKey(property, key);

root.components!.parameters![key] = { ...param };
const validKey = ensureValidComponentFixedFieldKey(program, property, key);
root.components!.parameters![validKey] = { ...param };
for (const key of Object.keys(param)) {
delete param[key];
}

param.$ref = "#/components/parameters/" + encodeURIComponent(key);
param.$ref = "#/components/parameters/" + encodeURIComponent(validKey);
}
}

Expand All @@ -1616,8 +1598,6 @@ function createOAPIEmitter(
const schemas = root.components!.schemas!;
const declarations = files[0].globalScope.declarations;
for (const declaration of declarations) {
validateComponentFixedFieldKey(serviceNamespace, declaration.name);

schemas[declaration.name] = declaration.value as any;
}
}
Expand Down
16 changes: 15 additions & 1 deletion packages/openapi3/src/schema-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ import {
OpenAPI3SchemaProperty,
OpenAPISchema3_1,
} from "./types.js";
import { getDefaultValue, includeDerivedModel, isBytesKeptRaw, isStdType } from "./util.js";
import {
ensureValidComponentFixedFieldKey,
getDefaultValue,
includeDerivedModel,
isBytesKeptRaw,
isStdType,
} from "./util.js";
import { VisibilityUsageTracker } from "./visibility-usage.js";
import { XmlModule } from "./xml-module.js";

Expand Down Expand Up @@ -230,6 +236,7 @@ export class OpenAPI3SchemaEmitterBase<
const baseName = getOpenAPITypeName(program, model, this.#typeNameOptions());
const isMultipart = this.getContentType().startsWith("multipart/");
const name = isMultipart ? baseName + "MultiPart" : baseName;

return this.#createDeclaration(model, name, this.applyConstraints(model, schema as any));
}

Expand Down Expand Up @@ -501,6 +508,7 @@ export class OpenAPI3SchemaEmitterBase<

enumDeclaration(en: Enum, name: string): EmitterOutput<object> {
const baseName = getOpenAPITypeName(this.emitter.getProgram(), en, this.#typeNameOptions());

return this.#createDeclaration(en, baseName, new ObjectBuilder(this.enumSchema(en)));
}

Expand Down Expand Up @@ -805,6 +813,11 @@ export class OpenAPI3SchemaEmitterBase<
}

#createDeclaration(type: Type, name: string, schema: ObjectBuilder<any>) {
const skipNameValidation = type.kind === "Model" && type.templateMapper !== undefined;
if (!skipNameValidation) {
name = ensureValidComponentFixedFieldKey(this.emitter.getProgram(), type, name);
}

const refUrl = getRef(this.emitter.getProgram(), type);
if (refUrl) {
return {
Expand Down Expand Up @@ -835,6 +848,7 @@ export class OpenAPI3SchemaEmitterBase<
fullName,
Object.fromEntries(decl.scope.declarations.map((x) => [x.name, true])),
);

return decl;
}

Expand Down
31 changes: 31 additions & 0 deletions packages/openapi3/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Value,
} from "@typespec/compiler";
import { HttpOperation } from "@typespec/http";
import { createDiagnostic } from "./lib.js";
/**
* Checks if two objects are deeply equal.
*
Expand Down Expand Up @@ -158,3 +159,33 @@ export function getDefaultValue(
export function isBytesKeptRaw(program: Program, type: Type) {
return type.kind === "Scalar" && type.name === "bytes" && getEncode(program, type) === undefined;
}

export function ensureValidComponentFixedFieldKey(
program: Program,
type: Type,
oldKey: string,
): string {
if (isValidComponentFixedFieldKey(oldKey)) return oldKey;
reportInvalidKey(program, type, oldKey);
return createValidKey(oldKey);
}

function isValidComponentFixedFieldKey(key: string) {
const validPattern = /^[a-zA-Z0-9.\-_]+$/;
return validPattern.test(key);
}

function reportInvalidKey(program: Program, type: Type, key: string) {
const diagnostic = createDiagnostic({
code: "invalid-component-fixed-field-key",
format: {
value: key,
},
target: type,
});
return program.reportDiagnostic(diagnostic);
}

function createValidKey(invalidKey: string): string {
return invalidKey.replace(/[^a-zA-Z0-9.\-_]/g, "_");
}
Loading

0 comments on commit b742e16

Please sign in to comment.