asyncapi / saunter

Saunter is a code-first AsyncAPI documentation generator for dotnet.
https://www.asyncapi.com/
MIT License
199 stars 56 forks source link

NJsonSchema #103

Closed m-wild closed 3 years ago

m-wild commented 3 years ago

Fixes #60

Wow, this one has been a long time coming!

Thanks @JanEggers for all your help implementing this change 🙂 Also thanks to @RicoSuter for building and maintaing NJsonSchema, and for reaching out to suggest the idea.

RicoSuter commented 3 years ago

Wow, Saunter is now using NJS?

m-wild commented 3 years ago

Wow, Saunter is now using NJS?

Yep, all the custom JSON schema generation code had been deleted and we are using NJsonSchema for all of the "Schema" types in the AsyncAPI document :)

RicoSuter commented 3 years ago

@tehmantra I think the current schema generation config uses JSON Schema rules but actually AsyncAPI uses OpenApi3... (eg JSON Schema has different null handling than Open API ("type" array with "null" vs "nullable").

Do we need to introduce SchemaType = AsyncApi in NJsonSchema or can we just use OpenApi3 - assuming it is "always" in sync?

From https://www.asyncapi.com/docs/getting-started/coming-from-openapi

AsyncAPI is compatible with OpenAPI schemas.

m-wild commented 3 years ago

@RicoSuter I'm not 100% sure... The specification indicates its a superset of JSON schema (https://www.asyncapi.com/docs/specifications/v2.1.0#schemaObject)

This object is a superset of the JSON Schema Specification Draft 07.

But it also says

Definition of the message payload. It can be of any type but defaults to Schema object. It must match the schema format, including encoding type - e.g Avro should be inlined as either a YAML or JSON object NOT a string to be parsed as YAML or JSON.

Message payload in AsyncAPI can be any value, not just an AsyncAPI/OpenAPI schema. For instance, it could be an Avro schema.

Perhaps I should ask the core team which is preferable between JSON Schema or OpenAPI Schema.

RicoSuter commented 3 years ago

@darrelmiller can you answer this one? I thought that AsyncAPI schema is in sync with the OpenAPI one?

m-wild commented 3 years ago

@RicoSuter I've also discovered an issue with using the SchemaType and JsonReferenceConverter together.

I was trying to do the following:

[JsonConverter(typeof(JsonReferenceConverter))]
public class AsyncApiDocument : ICloneable
{
    // etc..
}

public string Serialize(AsyncApiDocument document)
{
    var serializerSettings = new JsonSerializerSettings
    {
        ContractResolver = JsonSchema.CreateJsonSerializerContractResolver(SchemaType.OpenApi3),
    };

    return JsonConvert.SerializeObject(document, serializerSettings);
}

But was always getting the properties as x-nullable and x-example!

Looks like the JsonReferenceConverter does not fully respect the current JsonSerializerSettings, only the Formatting.

https://github.com/RicoSuter/NJsonSchema/blob/288eb13e931562264ed9ddd12a83741c67a1ddc4/src/NJsonSchema/Converters/JsonReferenceConverter.cs#L55

So we may need to change this line to something like

var json = JsonConvert.SerializeObject(value, new JsonSerializerSettings
{
    ContractResolver = serializer.ContractResolver,
    Formatting = serializer.Formatting,
    // Maybe copy other properties here too?
});
derberg commented 3 years ago

Hi folks. AsyncAPI schema is a superset of JSON Schema Specification Draft 07, prior 3.1 OpenAPI was not actually in sync with JSON Schemas, in 3.1 I think they introduced an option to specify different JSON Schema dialect that you can use to provide payload schema.

In AsyncAPI the default schema is AsyncAPI Schema, so yeah, JSON Schema. But AsyncAPI also allows you to provide schemas in totally different formats, like OpenAPI Schema, RAML Data Types, Avro - you name it. So for example our parser is able to grab these custom schemas and parse them into JSON Schema (example https://github.com/asyncapi/avro-schema-parser). User needs to specify custom schema with schemaFormat property.

I hope that helps. Lemme know if you need more help.

RicoSuter commented 3 years ago

@derberg all this stuff makes me probably mentally ill :-) - I already have a big fight with all the edge cases, schema differences etc. over at OpenAPI...

General question: In AsyncAPI should we use a) { "type": ["null", "string"] } or b) { "type": "string", "nullable": true } to describe a nullable string?

RicoSuter commented 3 years ago

@tehmantra created a PR with some suggestions: https://github.com/tehmantra/saunter/pull/113

derberg commented 3 years ago

@RicoSuter default AsyncAPI schema is superset of JSON Schema so { "type": ["null", "string"] } is correct. Afaik { "type": "string", "nullable": true } is what you do with OpenAPI 3.0, but with OpenAPI 3.1 you can do { "type": ["null", "string"] } as since 3.1 they are compatible with JSON Schema.

now, what I wrote above is default 😉

as I mentioned, user can also share schemas in different formats, like for example user can have AsyncAPI file and OpenAPI 3.0 file and they can share common payload schemas following OpenAPI 3.0 schema. Example with nullable: true

asyncapi: 2.0.0
info:
  title: Example with OpenAPI
  version: 0.1.0
channels:
  example:
    publish:
      message:
        schemaFormat: 'application/vnd.oai.openapi;version=3.0.0'
        payload: # The following is an OpenAPI schema
          type: object
          properties:
            title:
              type: string
              nullable: true
            author:
              type: string
              example: Jack Johnson

So while parsing such document you need to check the schemaFormat and best convert OpenAPI 3.0 schema to pure JSON Schema. This is our JS parser for this use case -> https://github.com/asyncapi/openapi-schema-parser/

all this stuff makes me probably mentally ill :-)

how is your mental health now after my answer 😆