From d09cd628d7b587a986e17a6a4322f28795d65117 Mon Sep 17 00:00:00 2001 From: Ed Ball Date: Thu, 10 Aug 2023 20:27:05 -0700 Subject: [PATCH] Support nullable and datetime. --- Directory.Build.props | 2 +- example/ExampleApi.fsd | 13 ++++++++++++- example/output/fsd/SwaggerPetstore.fsd | 2 +- example/output/fsd/swagger/SwaggerPetstore.yaml | 1 + example/output/swagger/ExampleApi.json | 9 +++++++++ example/output/swagger/ExampleApi.yaml | 7 +++++++ example/output/swagger/fsd/ExampleApi.fsd | 5 +++++ .../SwaggerConversion.cs | 2 +- src/Facility.Definition.Swagger/SwaggerGenerator.cs | 11 +++++++++++ src/Facility.Definition.Swagger/SwaggerSchema.cs | 4 ++++ 10 files changed, 52 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 65113ae..25b3ab6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,7 +33,7 @@ - 2.10.1 + 2.11.0 diff --git a/example/ExampleApi.fsd b/example/ExampleApi.fsd index b5fb16b..ba5d440 100644 --- a/example/ExampleApi.fsd +++ b/example/ExampleApi.fsd @@ -247,7 +247,7 @@ service ExampleApi [http(from: header)] prices: decimal[]; - [http(from: body)] + [http(from: body, type: "text/sink")] text: string; } @@ -285,6 +285,9 @@ service ExampleApi /// The price of the widget. price: decimal; + + /// When the widget was created. + created: datetime; } /// A widget job. @@ -357,6 +360,8 @@ service ExampleApi [tag(name: widgets)] namedWidgets: map; + + ternary: nullable; } /// Identifies a widget field. @@ -397,6 +402,12 @@ service ExampleApi [obsolete(message: "This field was never used.")] oldField: string; } + + /// An external data type. + extern data ExternalDto; + + /// An external enum. + extern enum ExternalEnum; } # ExampleApi diff --git a/example/output/fsd/SwaggerPetstore.fsd b/example/output/fsd/SwaggerPetstore.fsd index be4d688..d76eb9f 100644 --- a/example/output/fsd/SwaggerPetstore.fsd +++ b/example/output/fsd/SwaggerPetstore.fsd @@ -296,7 +296,7 @@ service SwaggerPetstore quantity: int32; - shipDate: string; + shipDate: datetime; /// Order Status status: string; diff --git a/example/output/fsd/swagger/SwaggerPetstore.yaml b/example/output/fsd/swagger/SwaggerPetstore.yaml index 2e3f318..2ed7c09 100644 --- a/example/output/fsd/swagger/SwaggerPetstore.yaml +++ b/example/output/fsd/swagger/SwaggerPetstore.yaml @@ -392,6 +392,7 @@ definitions: format: int32 shipDate: type: string + format: date-time status: description: Order Status type: string diff --git a/example/output/swagger/ExampleApi.json b/example/output/swagger/ExampleApi.json index 08cbaf6..7b87d15 100644 --- a/example/output/swagger/ExampleApi.json +++ b/example/output/swagger/ExampleApi.json @@ -601,6 +601,11 @@ "description": "The price of the widget.", "type": "number", "format": "decimal" + }, + "created": { + "description": "When the widget was created.", + "type": "string", + "format": "date-time" } }, "x-remarks": "Additional DTO remarks.\n\n## Heading\n\nOnly top-level headings need to match a member name." @@ -791,6 +796,10 @@ "additionalProperties": { "$ref": "#/definitions/Widget" } + }, + "ternary": { + "type": "boolean", + "x-nullable": true } } }, diff --git a/example/output/swagger/ExampleApi.yaml b/example/output/swagger/ExampleApi.yaml index f51e1be..2be5aca 100644 --- a/example/output/swagger/ExampleApi.yaml +++ b/example/output/swagger/ExampleApi.yaml @@ -415,6 +415,10 @@ definitions: description: The price of the widget. type: number format: decimal + created: + description: When the widget was created. + type: string + format: date-time x-remarks: |- Additional DTO remarks. @@ -553,6 +557,9 @@ definitions: type: object additionalProperties: $ref: '#/definitions/Widget' + ternary: + type: boolean + x-nullable: true GetInfoResponse: type: object properties: diff --git a/example/output/swagger/fsd/ExampleApi.fsd b/example/output/swagger/fsd/ExampleApi.fsd index 3d3456b..2047e8a 100644 --- a/example/output/swagger/fsd/ExampleApi.fsd +++ b/example/output/swagger/fsd/ExampleApi.fsd @@ -270,6 +270,9 @@ service ExampleApi /// The price of the widget. price: decimal; + + /// When the widget was created. + created: datetime; } /// A preference. @@ -326,6 +329,8 @@ service ExampleApi namedStrings: map; namedWidgets: map; + + ternary: boolean; } data KitchenSink diff --git a/src/Facility.Definition.Swagger/SwaggerConversion.cs b/src/Facility.Definition.Swagger/SwaggerConversion.cs index a0e08f2..afda40d 100644 --- a/src/Facility.Definition.Swagger/SwaggerConversion.cs +++ b/src/Facility.Definition.Swagger/SwaggerConversion.cs @@ -430,7 +430,7 @@ private SwaggerResponse ResolveResponse(SwaggerResponse swaggerResponse, Service switch (swaggerSchema.Type ?? SwaggerSchemaType.Object) { case SwaggerSchemaType.String: - return swaggerSchema.Format == SwaggerSchemaTypeFormat.Byte ? "bytes" : "string"; + return swaggerSchema.Format == SwaggerSchemaTypeFormat.DateTime ? "datetime" : swaggerSchema.Format == SwaggerSchemaTypeFormat.Byte ? "bytes" : "string"; case SwaggerSchemaType.Number: return swaggerSchema.Format == SwaggerSchemaTypeFormat.Decimal ? "decimal" : "double"; diff --git a/src/Facility.Definition.Swagger/SwaggerGenerator.cs b/src/Facility.Definition.Swagger/SwaggerGenerator.cs index 0e9a59b..0cb2573 100644 --- a/src/Facility.Definition.Swagger/SwaggerGenerator.cs +++ b/src/Facility.Definition.Swagger/SwaggerGenerator.cs @@ -395,6 +395,8 @@ private static T GetTypeSchema(ServiceTypeInfo type) return new T { Type = SwaggerSchemaType.Number, Format = SwaggerSchemaTypeFormat.Decimal }; case ServiceTypeKind.Bytes: return new T { Type = SwaggerSchemaType.String, Format = SwaggerSchemaTypeFormat.Byte }; + case ServiceTypeKind.DateTime: + return new T { Type = SwaggerSchemaType.String, Format = SwaggerSchemaTypeFormat.DateTime }; case ServiceTypeKind.Object: return new T { Type = SwaggerSchemaType.Object }; case ServiceTypeKind.Error: @@ -409,6 +411,8 @@ private static T GetTypeSchema(ServiceTypeInfo type) return GetArrayOfSchema(type.ValueType!); case ServiceTypeKind.Map: return (T) (object) GetMapOfSchema(type.ValueType!); + case ServiceTypeKind.Nullable: + return (T) (object) GetNullableOfSchema(type.ValueType!); default: throw new InvalidOperationException("Unexpected field type kind: " + type.Kind); } @@ -500,6 +504,13 @@ private static SwaggerSchema GetMapOfSchema(ServiceTypeInfo type) }; } + private static SwaggerSchema GetNullableOfSchema(ServiceTypeInfo type) + { + var schema = GetTypeSchema(type); + schema.Nullable = true; + return schema; + } + private static object? ConvertJTokenToObject(JToken token) { if (token is JValue value) diff --git a/src/Facility.Definition.Swagger/SwaggerSchema.cs b/src/Facility.Definition.Swagger/SwaggerSchema.cs index 1ddd6d1..c9ab442 100644 --- a/src/Facility.Definition.Swagger/SwaggerSchema.cs +++ b/src/Facility.Definition.Swagger/SwaggerSchema.cs @@ -76,6 +76,10 @@ public class SwaggerSchema : ISwaggerSchema [YamlMember(Alias = "x-obsolete")] public bool? Obsolete { get; set; } // parameters, headers, schema + [JsonProperty("x-nullable")] + [YamlMember(Alias = "x-nullable")] + public bool? Nullable { get; set; } // schema + [JsonProperty("x-remarks")] [YamlMember(Alias = "x-remarks")] public string? Remarks { get; set; } // schema