[Test] Support readOnly on required properties (#996)
* Add refWithReadOnly * Update isRefLike() to populate refWithReadOnly * Add additional test file from mike kistler that should simulate the problem this branch is addressing * Update test/modelValidation/swaggers/specification/readonlyNotRequired/openapi.json Co-authored-by: Ke Yu <v-ky@microsoft.com> Co-authored-by: Scott Beddall (from Dev Box) <scbedd@microsoft.com> Co-authored-by: Scott Beddall <45376673+scbedd@users.noreply.github.com> Co-authored-by: Mike Kistler <mikekistler@microsoft.com>
This commit is contained in:
Родитель
61fe0ba253
Коммит
fed7c550d9
|
@ -231,6 +231,10 @@ export class JsonLoader implements Loader<Json> {
|
|||
keepRefSiblings?: boolean
|
||||
): Promise<Json> {
|
||||
if (isRefLike(object)) {
|
||||
const refObjResult: any = {};
|
||||
if (object.readOnly !== undefined) {
|
||||
refObjResult.refWithReadOnly = object.readOnly;
|
||||
}
|
||||
const ref = object.$ref;
|
||||
const sp = ref.split("#");
|
||||
if (sp.length > 2) {
|
||||
|
@ -248,7 +252,8 @@ export class JsonLoader implements Loader<Json> {
|
|||
object.$ref = `${mockName}#${refObjPath}`;
|
||||
return object;
|
||||
}
|
||||
return { $ref: `${mockName}#${refObjPath}` };
|
||||
refObjResult.$ref = `${mockName}#${refObjPath}`;
|
||||
return refObjResult;
|
||||
}
|
||||
const refObj = await this.load(
|
||||
pathJoin(pathDirname(relativeFilePath), refFilePath),
|
||||
|
@ -263,13 +268,15 @@ export class JsonLoader implements Loader<Json> {
|
|||
object.$ref = `${refMockName}#${refObjPath}`;
|
||||
return object;
|
||||
}
|
||||
return { $ref: `${refMockName}#${refObjPath}` };
|
||||
refObjResult.$ref = `${refMockName}#${refObjPath}`;
|
||||
return refObjResult;
|
||||
} else {
|
||||
if (keepRefSiblings) {
|
||||
object.$ref = refMockName;
|
||||
return object;
|
||||
}
|
||||
return { $ref: refMockName };
|
||||
refObjResult.$ref = refMockName;
|
||||
return refObjResult;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,4 +336,5 @@ export class JsonLoader implements Loader<Json> {
|
|||
}
|
||||
}
|
||||
|
||||
export const isRefLike = (obj: any): obj is { $ref: string } => typeof obj.$ref === "string";
|
||||
export const isRefLike = (obj: any): obj is { $ref: string; readOnly?: boolean } =>
|
||||
typeof obj.$ref === "string";
|
||||
|
|
|
@ -247,6 +247,7 @@ export interface Schema extends BaseSchema {
|
|||
[xmsDiscriminatorValue]?: string;
|
||||
readOnly?: boolean;
|
||||
[xmsMutability]?: Array<"create" | "read" | "update">;
|
||||
refWithReadOnly?: boolean;
|
||||
xml?: XML;
|
||||
externalDocs?: ExternalDocs;
|
||||
example?: { [exampleName: string]: Example };
|
||||
|
|
|
@ -300,6 +300,16 @@ const shouldSkipError = (error: ErrorObject, cxt: SchemaValidateContext) => {
|
|||
return true;
|
||||
}
|
||||
|
||||
// If a request is missing a required property that is readOnly we can skip this error
|
||||
if (
|
||||
!cxt.isResponse &&
|
||||
keyword === "required" &&
|
||||
(parentSchema.properties?.[(params as any).missingProperty]?.refWithReadOnly ||
|
||||
parentSchema.properties?.[(params as any).missingProperty]?.readOnly)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a response has property which x-ms-secret value is "true" in post we can skip this error
|
||||
if (
|
||||
cxt.isResponse &&
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"title": "Create Widget",
|
||||
"operationId": "Widgets_Create",
|
||||
"parameters": {
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"body": {
|
||||
"description": "Description for sampleWidget",
|
||||
"secret": "don't tell anyone but your mother",
|
||||
"topSecret": "don't tell anyone"
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"body": {
|
||||
"widgetId": "00000000-0000-0000-0000-000000000000",
|
||||
"description": "Description for sampleWidget",
|
||||
"state": "Active"
|
||||
}
|
||||
},
|
||||
"201": {
|
||||
"body": {
|
||||
"widgetId": "00000000-0000-0000-0000-000000000000",
|
||||
"description": "Description for sampleWidget",
|
||||
"state": "Active"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Widget Service",
|
||||
"version": "1.0.0",
|
||||
"x-typespec-generated": [
|
||||
{
|
||||
"emitter": "@azure-tools/typespec-autorest"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"name": "Widgets"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/widgets/{id}": {
|
||||
"patch": {
|
||||
"operationId": "Widgets_Create",
|
||||
"tags": [
|
||||
"Widgets"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/WidgetUpdate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The request has succeeded.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Widget"
|
||||
}
|
||||
},
|
||||
"201": {
|
||||
"description": "The request has succeeded and a new resource has been created as a result.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Widget"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-ms-examples": {
|
||||
"Widgets_Create": {
|
||||
"$ref": "./CreateWidget.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"code",
|
||||
"message"
|
||||
]
|
||||
},
|
||||
"Widget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"widgetId": {
|
||||
"type": "string",
|
||||
"description": "The widget id.",
|
||||
"readOnly": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The widget description."
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/WidgetState",
|
||||
"description": "The widget state.",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"widgetId",
|
||||
"state"
|
||||
]
|
||||
},
|
||||
"WidgetState": {
|
||||
"type": "string",
|
||||
"description": "The widget state.",
|
||||
"enum": [
|
||||
"Active",
|
||||
"Expired"
|
||||
],
|
||||
"x-ms-enum": {
|
||||
"name": "WidgetState",
|
||||
"modelAsString": true,
|
||||
"values": [
|
||||
{
|
||||
"name": "Active",
|
||||
"value": "Active",
|
||||
"description": "The widget is Active."
|
||||
},
|
||||
{
|
||||
"name": "Expired",
|
||||
"value": "Expired",
|
||||
"description": "The widget is Expired."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"WidgetUpdate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The widget description."
|
||||
},
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"description": "A secret value.",
|
||||
"x-ms-mutability": [
|
||||
"update",
|
||||
"create"
|
||||
]
|
||||
},
|
||||
"topSecret": {
|
||||
"$ref": "#/definitions/secret",
|
||||
"description": "A top secret value.",
|
||||
"x-ms-mutability": [
|
||||
"update",
|
||||
"create"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"secret": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"parameters": {}
|
||||
}
|
|
@ -894,5 +894,12 @@ describe("Model Validation", () => {
|
|||
assert.strictEqual(result[0].message, "Expected type object but found type integer");
|
||||
assert.strictEqual(result[0].exampleJsonPath, "$responses.200.body.result1['id']");
|
||||
});
|
||||
|
||||
it("should validate mutable readonly properties without erroring", async() => {
|
||||
const specPath = `${testPath}/modelValidation/swaggers/specification/readonlyNotRequired/openapi.json`;
|
||||
const result = await validate.validateExamples(specPath, "Widgets_Create");
|
||||
|
||||
assert.strictEqual(result.length, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче