Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions Nancy.Metadata.Swagger.DemoApplication/Modules/RootModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public RootModule() : base("/api")
Get["SimpleRequest", "/hello"] = r => HelloWorld();
Get["SimpleRequestWithParameter", "/hello/{name}"] = r => Hello(r.name);

Post["SimplePostRequst", "/hello"] = r => HelloPost();
Post["SimplePostRequest", "/hello"] = r => HelloPost();
Post["PostRequestWithModel", "/hello/model"] = r => HelloModel();

Post["PostRequestWithNestedModel", "/hello/nestedmodel"] = r => HelloNestedModel();
Expand All @@ -25,7 +25,7 @@ private Response HelloNestedModel()

SimpleResponseModel response = new SimpleResponseModel
{
Hello = string.Format("Hello, {0}. We got your name from nested obejct", model.SimpleModel.Name)
Hello = string.Format("Hello, {0}. We got your name from nested object", model.SimpleModel.Name)
};

return Response.AsJson(response);
Expand Down Expand Up @@ -79,29 +79,29 @@ public class RootMetadataModule : MetadataModule<SwaggerRouteMetadata>
public RootMetadataModule()
{
Describe["SimpleRequest"] = desc => new SwaggerRouteMetadata(desc)
.With(i => i.WithResponseModel("200", typeof(SimpleResponseModel), "Sample response")
.WithSummary("Simple GET example"));
.With(i => SwaggerEndpointInfoBuilder.NewEndpointInfo(i).WithResponseModel("200", typeof(SimpleResponseModel), "Sample response")
.WithSummary("Simple GET example").Build());

Describe["SimpleRequestWithParameter"] = desc => new SwaggerRouteMetadata(desc)
.With(i => i.WithResponseModel("200", typeof(SimpleResponseModel), "Sample response")
.With(i => SwaggerEndpointInfoBuilder.NewEndpointInfo(i).WithResponseModel("200", typeof(SimpleResponseModel), "Sample response")
.WithRequestParameter("name")
.WithSummary("Simple GET with parameters"));
.WithSummary("Simple GET with parameters").Build());

Describe["SimplePostRequst"] = desc => new SwaggerRouteMetadata(desc)
.With(info => info.WithResponseModel("200", typeof(SimpleResponseModel), "Sample response")
.WithSummary("Simple POST example"));
Describe["SimplePostRequest"] = desc => new SwaggerRouteMetadata(desc)
.With(info => SwaggerEndpointInfoBuilder.NewEndpointInfo(info).WithResponseModel("200", typeof(SimpleResponseModel), "Sample response")
.WithSummary("Simple POST example").Build());

Describe["PostRequestWithModel"] = desc => new SwaggerRouteMetadata(desc)
.With(info => info.WithResponseModel("200", typeof(SimpleResponseModel))
.With(info => SwaggerEndpointInfoBuilder.NewEndpointInfo(info).WithResponseModel("200", typeof(SimpleResponseModel))
.WithResponse("400", "Bad request")
.WithSummary("Simple POST example with request model")
.WithRequestModel(typeof(SimpleRequestModel)));
.WithRequestModel(typeof(SimpleRequestModel)).Build());

Describe["PostRequestWithNestedModel"] = desc => new SwaggerRouteMetadata(desc)
.With(info => info.WithResponseModel("200", typeof(SimpleResponseModel))
.With(info => SwaggerEndpointInfoBuilder.NewEndpointInfo(info).WithResponseModel("200", typeof(SimpleResponseModel))
.WithResponse("400", "Bad request")
.WithSummary("Simple POST example with nested request model")
.WithRequestModel(typeof(NestedRequestModel)));
.WithRequestModel(typeof(NestedRequestModel)).Build());
}
}
}
9 changes: 9 additions & 0 deletions Nancy.Metadata.Swagger/Core/IJsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Nancy.Metadata.Swagger.Core
{
public interface IJsonSchemaGenerator
{
string GenerateSchema(Type modelType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using Newtonsoft.Json.Schema.Generation;

namespace Nancy.Metadata.Swagger.Core
{
public class NewtonsoftJsonSchemaGeneratorAdapter : IJsonSchemaGenerator
{
private readonly JSchemaGenerator jsonSchemaGenerator;

public NewtonsoftJsonSchemaGeneratorAdapter(JSchemaGenerator jsonSchemaGenerator)
{
this.jsonSchemaGenerator = jsonSchemaGenerator;
}

public string GenerateSchema(Type modelType)
{
return jsonSchemaGenerator.Generate(modelType).ToString();
}
}
}
5 changes: 3 additions & 2 deletions Nancy.Metadata.Swagger/Core/SchemaCache.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Collections.Generic;
using Newtonsoft.Json.Schema;

namespace Nancy.Metadata.Swagger.Core
{
using Newtonsoft.Json.Linq;

public static class SchemaCache
{
public static Dictionary<string, JSchema> Cache = new Dictionary<string, JSchema>();
public static Dictionary<string, JObject> Cache = new Dictionary<string, JObject>();
}
}
264 changes: 264 additions & 0 deletions Nancy.Metadata.Swagger/Fluent/SwaggerEndpointInfoBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Nancy.Metadata.Swagger.Core;
using Nancy.Metadata.Swagger.Model;
using Newtonsoft.Json.Schema.Generation;
using Newtonsoft.Json.Linq;

namespace Nancy.Metadata.Swagger.Fluent
{
public class SwaggerEndpointInfoBuilder
{
private readonly SwaggerEndpointInfo baseEndpointInfo;
private readonly IJsonSchemaGenerator jsonSchemaGenerator;

private Dictionary<string, SwaggerResponseInfo> responseInfos;
private List<SwaggerRequestParameter> requestParameters;
private string[] endpointTags;
private string endpointDescription;
private string endpointSummary;

private SwaggerEndpointInfoBuilder(SwaggerEndpointInfo baseEndpointInfo, IJsonSchemaGenerator jsonSchemaGenerator)
{
this.baseEndpointInfo = baseEndpointInfo;
this.jsonSchemaGenerator = jsonSchemaGenerator;
}

public static SwaggerEndpointInfoBuilder NewEndpointInfo()
{
return NewEndpointInfo(GetDefaultJsonSchemaGenerator());
}

public static SwaggerEndpointInfoBuilder NewEndpointInfo(IJsonSchemaGenerator jsonSchemaGenerator)
{
return NewEndpointInfo(new SwaggerEndpointInfo(), jsonSchemaGenerator);
}

public static SwaggerEndpointInfoBuilder NewEndpointInfo(SwaggerEndpointInfo baseEndpointInfo)
{
return NewEndpointInfo(baseEndpointInfo, GetDefaultJsonSchemaGenerator());
}

public static SwaggerEndpointInfoBuilder NewEndpointInfo(SwaggerEndpointInfo baseEndpointInfo, IJsonSchemaGenerator jsonSchemaGenerato)
{
return new SwaggerEndpointInfoBuilder(baseEndpointInfo, jsonSchemaGenerato);
}

public SwaggerEndpointInfoBuilder WithResponseModel(string statusCode, Type modelType, string description = null)
{
if (responseInfos == null)
{
responseInfos = new Dictionary<string, SwaggerResponseInfo>();
}

responseInfos[statusCode] = GenerateResponseInfo(description, modelType);

return this;
}

public SwaggerEndpointInfoBuilder WithDefaultResponse(Type responseType, string description = "Default response")
{
return WithResponseModel("200", responseType, description);
}

public SwaggerEndpointInfoBuilder WithResponse(string statusCode, string description)
{
if (responseInfos == null)
{
responseInfos = new Dictionary<string, SwaggerResponseInfo>();
}

responseInfos[statusCode] = GenerateResponseInfo(description);

return this;
}

public SwaggerEndpointInfoBuilder WithRequestParameter(string name,
string type = "string", string format = null, bool required = true, string description = null,
string loc = "path")
{
if (requestParameters == null)
{
requestParameters = new List<SwaggerRequestParameter>();
}

requestParameters.Add(new SwaggerRequestParameter
{
Required = required,
Description = description,
Format = format,
In = loc,
Name = name,
Type = type
});

return this;
}

public SwaggerEndpointInfoBuilder WithRequestModel(Type requestType, string name = "body", string description = null, bool required = true, string loc = "body")
{
if (requestParameters == null)
{
requestParameters = new List<SwaggerRequestParameter>();
}

requestParameters.Add(new SwaggerRequestParameter
{
Required = required,
Description = description,
In = loc,
Name = name,
Schema = new SchemaRef
{
Ref = $"#/{SwaggerConstants.ModelDefinitionsKey}/{GetOrSaveSchemaReference(requestType)}"
}
});

return this;
}

public SwaggerEndpointInfoBuilder WithDescription(string description, params string[] tags)
{
if (endpointTags == null)
{
if (tags.Length == 0)
{
tags = new[] { "default" };
}

endpointTags = tags;
}

endpointDescription = description;

return this;
}

public SwaggerEndpointInfoBuilder WithSummary(string summary)
{
endpointSummary = summary;
return this;
}

public SwaggerEndpointInfo Build()
{
baseEndpointInfo.Summary = endpointSummary;
baseEndpointInfo.Description = endpointDescription;
baseEndpointInfo.Tags = endpointTags;
if (responseInfos != null)
{
baseEndpointInfo.ResponseInfos = responseInfos;
}

if (requestParameters != null)
{
baseEndpointInfo.RequestParameters = requestParameters;
}

return baseEndpointInfo;
}

private SwaggerResponseInfo GenerateResponseInfo(string description, Type responseType)
{
return new SwaggerResponseInfo
{
Schema = new SchemaRef
{
Ref = "#/definitions/" + GetOrSaveSchemaReference(responseType)
},
Description = description
};
}

private SwaggerResponseInfo GenerateResponseInfo(string description)
{
return new SwaggerResponseInfo
{
Description = description
};
}

private string GetOrSaveSchemaReference(Type type)
{
var key = type.FullName;

if (SchemaCache.Cache.ContainsKey(key))
{
return key;
}

var schema = jsonSchemaGenerator.GenerateSchema(type);

// I didn't find the way how to disallow JSchemaGenerator to use nullable types, swagger doesn't work with them
var replaceNullableTypesPattern = @"\""type\"":[\s\n\r]*\[[\s\n\r]*\""(\w+)\"",[\s\n\r]*\""null\""[\s\n\r]*\]";
var fixedNullableTypesSchema = Regex.Replace(schema, replaceNullableTypesPattern, "\"type\": \"$1\"");
var fixedSchema = FixInnerDefinitionReferences(fixedNullableTypesSchema, key);
SchemaCache.Cache[key] = JObject.Parse(fixedSchema);

return key;
}

private string FixInnerDefinitionReferences(string jsonSchema, string parentDefinitionKey)
{
var jObject = JObject.Parse(jsonSchema);
foreach (var modelShemaToken in jObject)
{
if (modelShemaToken.Key.Equals(SwaggerConstants.TypePropertiesKey, StringComparison.InvariantCultureIgnoreCase))
{
FixSchemaReferenceForComplexProperty(parentDefinitionKey, modelShemaToken.Value as JObject);
}
if (modelShemaToken.Key.Equals(SwaggerConstants.AllOfKey, StringComparison.InvariantCultureIgnoreCase))
{
FixSchemaReferenceForBaseTypeDefinition(parentDefinitionKey, modelShemaToken.Value as JArray);
}
}

return jObject.ToString();
}

private void FixSchemaReferenceForBaseTypeDefinition(string parentDefinitionKey, JArray baseTypeDefinitions)
{
if (baseTypeDefinitions == null) return;
foreach (var baseTypeDefinitionItem in baseTypeDefinitions)
{
var baseTypeDefinition = baseTypeDefinitionItem as JObject;
var schemaReferenceProperty = baseTypeDefinition?.Property(SwaggerConstants.SchemaReferenceKey);
if (schemaReferenceProperty == null) continue;
UpdateSchemaReference(parentDefinitionKey, schemaReferenceProperty.Value);
}
}

private static void FixSchemaReferenceForComplexProperty(string parentDefinitionKey, JObject propertiesObject)
{
if (propertiesObject == null) return;
foreach (var propertyToken in propertiesObject)
{
var propertyObject = propertyToken.Value as JObject;
if (propertyObject == null) continue;
foreach (var modelReference in propertyObject)
{
if (modelReference.Key != SwaggerConstants.SchemaReferenceKey) continue;
UpdateSchemaReference(parentDefinitionKey, modelReference.Value);
}
}
}

private static void UpdateSchemaReference(string parentDefinitionKey, JToken modelReferenceValue)
{
var currentReference = modelReferenceValue.ToString();
var updatedReference = currentReference.Replace("#/", $"#/definitions/{parentDefinitionKey}/");
modelReferenceValue.Replace(new JValue(updatedReference));
}

private static IJsonSchemaGenerator GetDefaultJsonSchemaGenerator()
{
return new NewtonsoftJsonSchemaGeneratorAdapter(
new JSchemaGenerator
{
SchemaIdGenerationHandling = SchemaIdGenerationHandling.FullTypeName,
SchemaReferenceHandling = SchemaReferenceHandling.None
});
}
}
}
Loading