microsoft / typespec

https://typespec.io/
MIT License
4.53k stars 220 forks source link

Allow customized generating of additionalProperties in openapi 3 emitter #3241

Open genek96 opened 7 months ago

genek96 commented 7 months ago

Clear and concise description of the problem

Motivation

I use typespec (compiler version 0.54.0) to describe my API and generate openapi3 specification (@typespec/openapi3 emitter of version 0.54.0). This specification is shared across different commands to generate client for my service. One of the clients is written in c# and uses NSwag generator.

The problem is that generated openapi spec does not contain explicitly defined attribute additionalProperties: false. It leads to generating IDictionary<string, object> AdditionalProperties for each generated type. Related issue in nswag.

Seems like that the reason is json schema validation specification

Example

Model in typespec:

model Country {
  id: CountryId;
  alpha2Code: CountryAlpha2Code;
  name: Name;
}

Generated OpenApi:

Dicts.Country:
      type: object
      required:
        - id
        - alpha2Code
        - name
      properties:
        id:
          $ref: '#/components/schemas/Dicts.CountryId'
        alpha2Code:
          $ref: '#/components/schemas/Dicts.CountryAlpha2Code'
        name:
          $ref: '#/components/schemas/Dicts.Name'

Generated c# code:

public partial class Country
    {
        [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Id { get; set; }

        [Newtonsoft.Json.JsonProperty("alpha2Code", Required = Newtonsoft.Json.Required.Always)]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        [System.ComponentModel.DataAnnotations.RegularExpression(@"^[A-Z]{3}$")]
        public string Alpha2Code { get; set; }

        [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)]
        [System.ComponentModel.DataAnnotations.Required]
        public Name Name { get; set; } = new Name();

        private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

        [Newtonsoft.Json.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

Proposal

It will be very useful to have opportunity to specify emitter option - specify explicitly additional properties or not. Like that:

options:
  "@typespec/openapi3":
    explicitAdditionalPropertiesFalse: true

or:

options:
  "@typespec/openapi3":
    useDefaultAdditionalPropertiesValue: "false"

It should produce the following openapi:

    Dicts.Country:
      type: object
      required:
        - id
        - alpha2Code
        - name
      additionapProperties: false
      properties:
        id:
          $ref: '#/components/schemas/Dicts.CountryId'
        alpha2Code:
          $ref: '#/components/schemas/Dicts.CountryAlpha2Code'
        name:
          $ref: '#/components/schemas/Dicts.Name'

Checklist

adamscybot commented 5 months ago

See https://github.com/microsoft/typespec/issues/3549#issuecomment-2166501680. Setting additionalProperties: false across all models would basically muddy intended semantics of extends syntax. It would have to be a decorator, and it would probably need a fair amount of build time error handling to prevent confusing situations.

However unevaluatedProperties probably makes sense as a schema-gen-wide config option.