Skip to content

[RequiredPropertiesMissingInResourceModel][BodyTopLevelProperties] Special-case the OperationStatus response #806

@mikeharder

Description

@mikeharder

This sample shows the recommended TypeSpec (and generated Swagger) for an OperationStatus response: playground

This swagger causes the following errors:

error   | RequiredPropertiesMissingInResourceModel | Model definition 'OperationStatus' must have the properties 'name', 'id' and 'type' in its hierarchy and these properties must be marked as readonly.
    - file:///home/mharder/specs-mh/specification/widget/resource-manager/Microsoft.Widget/Widget/stable/2021-11-01/widget.json:154:5
error   | BodyTopLevelProperties | Top level properties should be one of name, type, id, location, properties, tags, plan, sku, etag, managedBy, identity, zones. Model definition 'OperationStatus' has extra properties ['status'].
    - file:///home/mharder/specs-mh/specification/widget/resource-manager/Microsoft.Widget/Widget/stable/2021-11-01/widget.json:154:5
error   | BodyTopLevelProperties | Top level properties should be one of name, type, id, location, properties, tags, plan, sku, etag, managedBy, identity, zones. Model definition 'OperationStatus' has extra properties ['startTime'].
    - file:///home/mharder/specs-mh/specification/widget/resource-manager/Microsoft.Widget/Widget/stable/2021-11-01/widget.json:154:5
error   | BodyTopLevelProperties | Top level properties should be one of name, type, id, location, properties, tags, plan, sku, etag, managedBy, identity, zones. Model definition 'OperationStatus' has extra properties ['endTime'].
    - file:///home/mharder/specs-mh/specification/widget/resource-manager/Microsoft.Widget/Widget/stable/2021-11-01/widget.json:154:5
error   | BodyTopLevelProperties | Top level properties should be one of name, type, id, location, properties, tags, plan, sku, etag, managedBy, identity, zones. Model definition 'OperationStatus' has extra properties ['percentComplete'].
    - file:///home/mharder/specs-mh/specification/widget/resource-manager/Microsoft.Widget/Widget/stable/2021-11-01/widget.json:154:5
error   | BodyTopLevelProperties | Top level properties should be one of name, type, id, location, properties, tags, plan, sku, etag, managedBy, identity, zones. Model definition 'OperationStatus' has extra properties ['error'].
    - file:///home/mharder/specs-mh/specification/widget/resource-manager/Microsoft.Widget/Widget/stable/2021-11-01/widget.json:154:5

We believe this swagger should cause no errors. The rules RequiredPropertiesMissingInResourceModel and BodyTopLevelProperties should be updated to allow (or require?) the schema in this sample.

Example existing spec, that fails with these errors (they were ignored when the PR was merged to main):

https://github.com/Azure/azure-rest-api-specs/blob/fda1bb1d4593ae819edd699446d0941d1e8f1174/specification/chaos/resource-manager/Microsoft.Chaos/Chaos/stable/2024-01-01/operationStatuses.json#L37-L83

Examples (from playground)

TypeSpec

import "@typespec/http";
import "@typespec/rest";
import "@typespec/versioning";
import "@azure-tools/typespec-azure-core";
import "@azure-tools/typespec-azure-resource-manager";

using Http;
using Rest;
using Versioning;
using Azure.Core;
using Azure.ResourceManager;

/** Contoso Resource Provider management API. */
@armProviderNamespace
@service(#{ title: "ContosoProviderHubClient" })
@versioned(Versions)
namespace Microsoft.ContosoProviderHub;

/** Contoso API versions */
enum Versions {
  /** 2021-10-01-preview version */
  @armCommonTypesVersion(Azure.ResourceManager.CommonTypes.Versions.v5)
  `2021-10-01-preview`,
}

/** A ContosoProviderHub resource */
@parentResource(SubscriptionLocationResource)
model OperationStatus {
  /** The operation status */
  @Azure.Core.lroStatus
  status: Azure.ResourceManager.ResourceProvisioningState;

  /** The unique identifier for the operationStatus resource */
  @visibility(Lifecycle.Read)
  id: string;

  /** The name of the  operationStatus resource */
  @visibility(Lifecycle.Read)
  @key
  @path
  @segment("operationStatuses")
  name: string;

  /** Operation start time */
  @visibility(Lifecycle.Read)
  startTime?: utcDateTime;

  /** Operation complete time */
  @visibility(Lifecycle.Read)
  endTime?: utcDateTime;

  /** The progress made toward completing the operation */
  @visibility(Lifecycle.Read)
  percentComplete?: float64;

  /** rp-specific properties */
  properties?: OperationStatusProperties;

  /** Errors that occurred if the operation ended with Canceled or Failed status */
  @visibility(Lifecycle.Read)
  error?: Azure.ResourceManager.Foundations.ErrorDetail;
}

/** Employee properties */
model OperationStatusProperties {
  /** Age of employee */
  age?: int32;

  /** City of employee */
  city?: string;

  /** Profile of employee */
  @encode("base64url")
  profile?: bytes;
}

interface Operations extends Azure.ResourceManager.Operations {}

@armResourceOperations
interface Employees {
  get is ArmResourceRead<OperationStatus>;
}

Swagger

{
  "swagger": "2.0",
  "info": {
    "title": "ContosoProviderHubClient",
    "version": "2021-10-01-preview",
    "description": "Contoso Resource Provider management API.",
    "x-typespec-generated": [
      {
        "emitter": "@azure-tools/typespec-autorest"
      }
    ]
  },
  "schemes": [
    "https"
  ],
  "host": "management.azure.com",
  "produces": [
    "application/json"
  ],
  "consumes": [
    "application/json"
  ],
  "security": [
    {
      "azure_auth": [
        "user_impersonation"
      ]
    }
  ],
  "securityDefinitions": {
    "azure_auth": {
      "type": "oauth2",
      "description": "Azure Active Directory OAuth2 Flow.",
      "flow": "implicit",
      "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize",
      "scopes": {
        "user_impersonation": "impersonate your user account"
      }
    }
  },
  "tags": [
    {
      "name": "Operations"
    },
    {
      "name": "Employees"
    }
  ],
  "paths": {
    "/providers/Microsoft.ContosoProviderHub/operations": {
      "get": {
        "operationId": "Operations_List",
        "tags": [
          "Operations"
        ],
        "description": "List the operations for the provider",
        "parameters": [
          {
            "$ref": "../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Azure operation completed successfully.",
            "schema": {
              "$ref": "../../../common-types/resource-management/v5/types.json#/definitions/OperationListResult"
            }
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse"
            }
          }
        },
        "x-ms-pageable": {
          "nextLinkName": "nextLink"
        }
      }
    },
    "/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/locations/{location}/operationStatuses/{name}": {
      "get": {
        "operationId": "Employees_Get",
        "tags": [
          "Employees"
        ],
        "description": "Get a OperationStatus",
        "parameters": [
          {
            "$ref": "../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter"
          },
          {
            "$ref": "../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter"
          },
          {
            "$ref": "../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter"
          },
          {
            "name": "name",
            "in": "path",
            "description": "The name of the  operationStatus resource",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Azure operation completed successfully.",
            "schema": {
              "$ref": "#/definitions/OperationStatus"
            }
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "Azure.ResourceManager.ResourceProvisioningState": {
      "type": "string",
      "description": "The provisioning state of a resource type.",
      "enum": [
        "Succeeded",
        "Failed",
        "Canceled"
      ],
      "x-ms-enum": {
        "name": "ResourceProvisioningState",
        "modelAsString": true,
        "values": [
          {
            "name": "Succeeded",
            "value": "Succeeded",
            "description": "Resource has been created."
          },
          {
            "name": "Failed",
            "value": "Failed",
            "description": "Resource creation failed."
          },
          {
            "name": "Canceled",
            "value": "Canceled",
            "description": "Resource creation was canceled."
          }
        ]
      }
    },
    "OperationStatus": {
      "type": "object",
      "description": "A ContosoProviderHub resource",
      "properties": {
        "status": {
          "$ref": "#/definitions/Azure.ResourceManager.ResourceProvisioningState",
          "description": "The operation status"
        },
        "id": {
          "type": "string",
          "description": "The unique identifier for the operationStatus resource",
          "readOnly": true
        },
        "name": {
          "type": "string",
          "description": "The name of the  operationStatus resource",
          "readOnly": true
        },
        "startTime": {
          "type": "string",
          "format": "date-time",
          "description": "Operation start time",
          "readOnly": true
        },
        "endTime": {
          "type": "string",
          "format": "date-time",
          "description": "Operation complete time",
          "readOnly": true
        },
        "percentComplete": {
          "type": "number",
          "format": "double",
          "description": "The progress made toward completing the operation",
          "readOnly": true
        },
        "properties": {
          "$ref": "#/definitions/OperationStatusProperties",
          "description": "rp-specific properties"
        },
        "error": {
          "$ref": "../../../common-types/resource-management/v5/types.json#/definitions/ErrorDetail",
          "description": "Errors that occurred if the operation ended with Canceled or Failed status",
          "readOnly": true
        }
      },
      "required": [
        "status",
        "id",
        "name"
      ]
    },
    "OperationStatusProperties": {
      "type": "object",
      "description": "Employee properties",
      "properties": {
        "age": {
          "type": "integer",
          "format": "int32",
          "description": "Age of employee"
        },
        "city": {
          "type": "string",
          "description": "City of employee"
        },
        "profile": {
          "type": "string",
          "format": "base64url",
          "description": "Profile of employee"
        }
      }
    }
  },
  "parameters": {}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions