microsoft / OpenAPI.NET

The OpenAPI.NET SDK contains a useful object model for OpenAPI documents in .NET along with common serializers to extract raw OpenAPI JSON and YAML documents from the model.
MIT License
1.42k stars 239 forks source link

Parsing "examples" fails #228

Closed brooksyott closed 6 years ago

brooksyott commented 6 years ago

If you use the keyword "examples:", either in JSON or YAML, it fails. In the case of YAML, an exception is thrown. In the case of JSON,

Cannot create map at #/paths//smsmessaging/{apiVersion}/{userId}/inbound/subscriptions/get/responses/200/content/examples

and it omits it from the conversion.

I used the OpenAPI workbench to validate my findings.

Sample JSON that fails below:

{ "openapi": "3.0.0", "info": { "title": "CPaaS SMS API", "description": "API for CPaaS 2.0 to use SMS related functions", "termsOfService": "https://kandy.io", "contact": { "name": "Kandy CPaaS API Team", "url": "https://kandy.io", "email": "fatih.demirel@kandy.io" }, "version": "0.2" }, "servers": [ { "url": "https://cpaas-api20-kvs1.kandy.io/{cpaasRoot}/", "variables": { "cpaasRoot": { "default": "cpaas", "description": "Root folder name configured based on the deployment" } } } ], "paths": { "/smsmessaging/{apiVersion}/{userId}/inbound/subscriptions": { "get": { "tags": [ "inboundSMS" ], "summary": "Read all active inbound SMS subscriptions", "description": "Read all active inbound SMS subscriptions", "operationId": "readAllInboundSMSSubscription", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/inboundAllSMSSubscriptionResponse" }, "examples": [ { "inboundAllSMSSubscriptionResponseExample": {
"$ref": "#/components/examples/inboundAllSMSSubscriptionResponseExample" } }, { "inboundAllSMSSubscriptionResponseExample": {
"$ref": "#/components/examples/inboundAllSMSSubscriptionResponseExample" } } ] } } }, "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/failureResponse" } } } } } }, "parameters": [ { "$ref": "#/components/parameters/apiVersion" }, { "$ref": "#/components/parameters/userId" } ] } }, "components": { "schemas": { "serviceException": { "type": "object", "properties": { "messageId": { "type": "string", "description": "Error response ID" }, "text": { "type": "string", "description": "Detailed explanation of the error condition" }, "variables": { "type": "array", "items": { "type": "string", "description": "Varible values appear within the text field" } } } }, "link": { "type": "object", "properties": { "href": { "type": "string", "description": "Relative path to the resource being called" }, "rel": { "enum": [ "smsmessaging" ], "type": "string" } } }, "requestError": { "type": "object", "properties": { "link": { "$ref": "#/components/schemas/link" }, "serviceException": { "$ref": "#/components/schemas/serviceException" } } }, "failureResponse": { "type": "object", "properties": { "requestError": { "$ref": "#/components/schemas/requestError" } } }, "callbackReference": { "required": [ "notifyURL" ], "type": "object", "properties": { "notifyURL": { "type": "string", "description": "The notification channel ID that has been acquired during /notificationchannel API subscription, either websocket or mobile push type, which the incoming notifications supposed to be sent to." } } }, "inboundSMSSubscriptionReq": { "required": [ "callbackReference", "clientCorrelator" ], "type": "object", "properties": { "callbackReference": { "$ref": "#/components/schemas/callbackReference" }, "clientCorrelator": { "$ref": "#/components/schemas/clientCorrelator" }, "destinationAddress": { "type": "string", "description": "The address that incoming messages are received. Current implementation assumes this value is the same with the {userId} path parameter." } } }, "inboundSMSSubscriptionResp": { "allOf": [ { "$ref": "#/components/schemas/inboundSMSSubscriptionReq" }, { "type": "object", "properties": { "resourceURL": { "$ref": "#/components/schemas/resourceURL" } } } ] }, "inboundSMSSubscriptionResponse": { "type": "object", "properties": { "subscription": { "$ref": "#/components/schemas/inboundSMSSubscriptionResp" } } }, "inboundSMSAllSubscriptionResp": { "type": "array", "items": { "$ref": "#/components/schemas/inboundSMSSubscriptionResp" } }, "resourceURL": { "type": "string", "description": "Relative path of the resource" }, "subscriptionList": { "type": "object", "properties": { "resourceURL": { "$ref": "#/components/schemas/resourceURL" }, "subscription": { "$ref": "#/components/schemas/inboundSMSAllSubscriptionResp" } } }, "inboundAllSMSSubscriptionResponse": { "type": "object", "properties": { "subscriptionList": { "$ref": "#/components/schemas/subscriptionList" } } }, "NotificationChannelRequest": { "required": [ "notificationChannel" ], "type": "object", "properties": { "notificationChannel": { "oneOf": [ { "$ref": "#/components/schemas/NotificationChannelWSReq" }, { "$ref": "#/components/schemas/NotificationChannelPushReq" } ] } } }, "NotificationChannelWSReq": { "required": [ "channelType", "clientCorrelator" ], "type": "object", "properties": { "applicationTag": { "$ref": "#/components/schemas/applicationTag" }, "channelLifetime": { "type": "integer", "description": "Indicates the channelLifetime value requested, in seconds." }, "channelType": { "enum": [ "Websockets" ], "type": "string" }, "clientCorrelator": { "$ref": "#/components/schemas/clientCorrelator" }, "x-connCheckRole": { "$ref": "#/components/schemas/x-connCheckRole" } } }, "NotificationChannelPushReq": { "required": [ "channelData", "channelType", "clientCorrelator" ], "type": "object", "properties": { "applicationTag": { "$ref": "#/components/schemas/applicationTag" }, "channelData": { "$ref": "#/components/schemas/ChannelDataPush" }, "channelType": { "enum": [ "OMAPush" ], "type": "string" }, "clientCorrelator": { "$ref": "#/components/schemas/clientCorrelator" } } }, "ChannelDataPush": { "type": "object", "properties": { "appId": { "type": "string", "description": "Defines application bundle ID on FCM\APNS" }, "x-deviceToken": { "type": "string", "description": "Indicates the token provided by Push provider (FCM\APNs)" }, "x-provider": { "enum": [ "apns", "fcm" ], "type": "string", "description": "Indicates which Push provider to be used" } } }, "x-connCheckRole": { "enum": [ "client", "server" ], "type": "string", "description": "Optional param, indicates if client wants to use application level websocket ping-pong (connCheck-connAck), and in which role the client desires to take." }, "clientCorrelator": { "type": "string", "description": "Indicates a unique ID for the client being used, where same ID should be provided for WebSocket, Push and other CPaaS services subscriptions and REST requests generated from the same device and application for correlation purposes." }, "applicationTag": { "type": "string", "description": "An optional tag for the application, not being used by CPaaS." } }, "parameters": { "apiVersion": { "name": "apiVersion", "in": "path", "description": "API version", "required": true, "schema": { "type": "string" }, "example": { "NotificationChannelResponseExample-WS": "", "$ref": "#/components/examples/NotificationChannelResponseExample-WS" } }, "subscriptionId": { "name": "subscriptionId", "in": "path", "description": "Resource ID of the subscription", "required": true, "schema": { "type": "string" } } } }, "tags": [ { "name": "inboundSMS", "description": "Resources related with inbound SMS subscription" } ] }

darrelmiller commented 6 years ago

Hey @brooksyott , thanks for the taking the time to report the issue. The problem you are running into is that the examples property in a media type object is a map and not an array. This was changed from v2, where it used to be an array. Also, now the examples object allows you to specify some metadata about the example.

brooksyott commented 6 years ago

Perhaps I gave a bad example. I may still be misunderstanding as well.

I believe from the openApi page on Swagger: https://swagger.io/docs/specification/adding-examples/

The following should work:

openapi: "3.0.0"
info:
  description:  API for CPaaS 2.0 to use SMS related functions
  version: "0.2"
  title: CPaaS SMS API
  termsOfService: https://kandy.io
  contact:
    name: Kandy CPaaS API Team
    email: no.email@kandy.io
    url: https://kandy.io
servers:
  - url: https://cpaas-api20-kvs1.kandy.io/{cpaasRoot}/
    variables:
      cpaasRoot:
        default: cpaas
        description: Root folder name configured based on the deployment

paths:
  /smsmessaging/{apiVersion}/{userId}/inbound/subscriptions: 

    parameters:
      - in: query
        name: limit
        schema:
          type: integer
          maximum: 50
        examples:       # Multiple examples
          zero:         # Distinct name
            value: 0    # Example value
            summary: A sample limit value # Optional description
          max: # Distinct name
            value: 50   # Example value
            summary: A sample limit value # Optional description

When I put that into the workbench (or use the library), I get: Failed to parse input: Unable to cast object of type 'Microsoft.OpenApi.Readers.ParseNodes.MapNode' to type 'Microsoft.OpenApi.Readers.ParseNodes.ListNode'.

If I change "examples" to "example", it does convert it without error?

darrelmiller commented 6 years ago

@brooksyott Yes, your example above should work. There is an error in the library. I will get that fixed ASAP. Thanks for the heads up. The reason it works when you change to example is because we accept any structure under example, but that is not what you are trying to do in this case.

brooksyott commented 6 years ago

Really appreciate the quick response, thank you!!