Created
February 7, 2024 06:31
-
-
Save gistlyn/ee17e71113ea571485f42bce91239df3 to your computer and use it in GitHub Desktop.
postman2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using ServiceStack; | |
using ServiceStack.DataAnnotations; | |
using ServiceStack.Host; | |
using ServiceStack.Model; | |
using ServiceStack.Text; | |
using ServiceStack.Web; | |
[assembly: HostingStartup(typeof(MyApp.ConfigurePostman))] | |
namespace MyApp; | |
public class ConfigurePostman : IHostingStartup | |
{ | |
public void Configure(IWebHostBuilder builder) => builder | |
.ConfigureServices(services => { | |
services.AddPlugin(new PostmanFeature()); | |
}); | |
} | |
public class PostmanFeature : IPlugin, IHasStringId | |
{ | |
public string Id { get; set; } = Plugins.Postman; | |
public string AtRestPath { get; set; } | |
public bool? EnableSessionExport { get; set; } | |
public string Headers { get; set; } | |
public List<string> DefaultLabelFmt { get; set; } | |
public readonly Dictionary<string, string> FriendlyTypeNames = new() { | |
{"Int32", "int"}, | |
{"Int64", "long"}, | |
{"Boolean", "bool"}, | |
{"String", "string"}, | |
{"Double", "double"}, | |
{"Single", "float"}, | |
}; | |
/// <summary> | |
/// Only generate specified Verb entries for "ANY" routes | |
/// </summary> | |
public List<string> DefaultVerbsForAny { get; set; } | |
public PostmanFeature() | |
{ | |
this.AtRestPath = "/postman"; | |
this.Headers = "Accept: " + MimeTypes.Json; | |
this.DefaultVerbsForAny = new List<string> { ServiceStack.HttpMethods.Get }; | |
this.DefaultLabelFmt = new List<string> { "type" }; | |
} | |
public void Register(IAppHost appHost) | |
{ | |
appHost.RegisterService<PostmanService>(AtRestPath); | |
appHost.GetPlugin<MetadataFeature>() | |
.AddPluginLink(AtRestPath.TrimStart('/'), "Postman Metadata"); | |
if (EnableSessionExport == null) | |
EnableSessionExport = appHost.Config.DebugMode; | |
} | |
} | |
[ExcludeMetadata] | |
public class Postman | |
{ | |
public List<string>? Label { get; set; } | |
public bool ExportSession { get; set; } | |
public string? ssid { get; set; } | |
public string? sspid { get; set; } | |
public string? ssopt { get; set; } | |
} | |
public class PostmanCollectionInfo | |
{ | |
public string? name { get; set; } | |
public string? version { get; set; } | |
public string? schema { get; set; } | |
} | |
public class PostmanCollection | |
{ | |
public PostmanCollectionInfo info { get; set; } = new(); | |
public List<PostmanRequest>? item { get; set; } | |
} | |
public class PostmanRequestBody | |
{ | |
public string mode { get; set; } = "formdata"; | |
public List<PostmanData>? formdata { get; set; } | |
} | |
public class PostmanRequestUrl | |
{ | |
public string raw { get; set; } | |
public string protocol { get; set; } | |
public string host { get; set; } | |
public string[] path { get; set; } | |
public string port { get; set; } | |
public List<PostmanRequestKeyValue> query { get; set; } | |
public List<PostmanRequestKeyValue> variable { get; set; } | |
} | |
public class PostmanRequestDetails | |
{ | |
public PostmanRequestUrl url { get; set; } | |
public string method { get; set; } | |
public string header { get; set; } | |
public PostmanRequestBody body { get; set; } | |
} | |
public class PostmanRequestKeyValue | |
{ | |
public string value { get; set; } | |
public string key { get; set; } | |
} | |
public class PostmanRequest | |
{ | |
public PostmanRequest() | |
{ | |
request = new PostmanRequestDetails(); | |
} | |
public string name { get; set; } | |
public PostmanRequestDetails request { get; set; } | |
} | |
public class PostmanData | |
{ | |
public string key { get; set; } | |
public string value { get; set; } | |
public string type { get; set; } | |
} | |
[DefaultRequest(typeof(Postman))] | |
[Restrict(VisibilityTo = RequestAttributes.None)] | |
public class PostmanService : Service | |
{ | |
[AddHeader(ContentType = MimeTypes.Json)] | |
public object Any(Postman request) | |
{ | |
var feature = HostContext.GetPlugin<PostmanFeature>(); | |
if (request.ExportSession) | |
{ | |
if (feature.EnableSessionExport != true) | |
throw new ArgumentException("PostmanFeature.EnableSessionExport is not enabled"); | |
var url = Request.GetBaseUrl() | |
.CombineWith(Request.PathInfo) | |
.AddQueryParam("ssopt", Request.GetItemOrCookie(SessionFeature.SessionOptionsKey)) | |
.AddQueryParam("sspid", Request.GetPermanentSessionId()) | |
.AddQueryParam("ssid", Request.GetTemporarySessionId()); | |
return HttpResult.Redirect(url); | |
} | |
var id = ServiceStack.SessionExtensions.CreateRandomSessionId(); | |
var ret = new PostmanCollection | |
{ | |
info = new PostmanCollectionInfo() | |
{ | |
version = "1", | |
name = HostContext.AppHost.ServiceName, | |
schema = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" | |
}, | |
item = GetRequests(request, id, HostContext.Metadata.OperationsMap.Values), | |
}; | |
return ret; | |
} | |
public List<PostmanRequest> GetRequests(Postman request, string parentId, IEnumerable<Operation> operations) | |
{ | |
var ret = new List<PostmanRequest>(); | |
var feature = HostContext.GetPlugin<PostmanFeature>(); | |
var headers = feature.Headers ?? ("Accept: " + MimeTypes.Json); | |
if (Response is IHttpResponse httpRes) | |
{ | |
if (request.ssopt != null | |
|| request.sspid != null | |
|| request.ssid != null) | |
{ | |
if (feature.EnableSessionExport != true) | |
throw new ArgumentException("PostmanFeature.EnableSessionExport is not enabled"); | |
} | |
if (request.ssopt != null) | |
{ | |
Request.AddSessionOptions(request.ssopt); | |
} | |
if (request.sspid != null) | |
{ | |
httpRes.Cookies.AddPermanentCookie(SessionFeature.PermanentSessionId, request.sspid); | |
} | |
if (request.ssid != null) | |
{ | |
httpRes.Cookies.AddSessionCookie(SessionFeature.SessionId, request.ssid, | |
(HostContext.Config.UseSecureCookies && Request.IsSecureConnection)); | |
} | |
} | |
foreach (var op in operations) | |
{ | |
Uri url = null; | |
if (!HostContext.Metadata.IsVisible(base.Request, op)) | |
continue; | |
var allVerbs = new HashSet<string>(op.Actions.Concat( | |
op.Routes.SelectMany(x => x.Verbs)) | |
.SelectMany(x => x == ActionContext.AnyAction | |
? feature.DefaultVerbsForAny | |
: new List<string> { x })); | |
var propertyTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | |
op.RequestType.GetSerializableFields() | |
.Each(x => propertyTypes[x.Name] = x.FieldType.AsFriendlyName(feature)); | |
op.RequestType.GetSerializableProperties() | |
.Each(x => propertyTypes[x.Name] = x.PropertyType.AsFriendlyName(feature)); | |
foreach (var route in op.Routes) | |
{ | |
var routeVerbs = route.Verbs.Contains(ActionContext.AnyAction) | |
? feature.DefaultVerbsForAny.ToArray() | |
: route.Verbs; | |
var restRoute = route.ToRestRoute(); | |
foreach (var verb in routeVerbs) | |
{ | |
allVerbs.Remove(verb); //exclude handled verbs | |
var routeData = restRoute.QueryStringVariables | |
.Map(x => new PostmanData | |
{ | |
key = x, | |
value = "", | |
type = "text", | |
}) | |
.ApplyPropertyTypes(propertyTypes); | |
url = new Uri(Request.GetBaseUrl().CombineWith(restRoute.Path.ToPostmanPathVariables())); | |
ret.Add(new PostmanRequest | |
{ | |
request = new PostmanRequestDetails { | |
url = new PostmanRequestUrl { | |
raw = url.OriginalString, | |
host = url.Host, | |
port = url.Port.ToString(), | |
protocol = url.Scheme, | |
path = url.LocalPath.SplitPaths(), | |
query = (!HttpUtils.HasRequestBody(verb) | |
? routeData.Select(x => x.key) | |
.ApplyPropertyTypes(propertyTypes) | |
.Map(x => new PostmanRequestKeyValue { key = x.Key, value = x.Value }) | |
: null)!, | |
variable = (restRoute.Variables.Any() | |
? restRoute.Variables.Map(x => new PostmanRequestKeyValue { key = x }) | |
: null)! | |
}, | |
method = verb, | |
body = new PostmanRequestBody { | |
formdata = (HttpUtils.HasRequestBody(verb) | |
? routeData | |
: null)!, | |
}, | |
header = headers, | |
}, | |
name = GetName(feature, request, op.RequestType, restRoute.Path), | |
}); | |
} | |
} | |
var emptyRequest = op.RequestType.CreateInstance(); | |
var virtualPath = emptyRequest.ToReplyUrlOnly(); | |
var requestParams = propertyTypes | |
.Map(x => new PostmanData | |
{ | |
key = x.Key, | |
value = x.Value, | |
type = "text", | |
}); | |
url = new Uri(Request.GetBaseUrl().CombineWith(virtualPath)); | |
ret.AddRange(allVerbs.Select(verb => | |
new PostmanRequest | |
{ | |
request = new PostmanRequestDetails { | |
url = new PostmanRequestUrl { | |
raw = url.OriginalString, | |
host = url.Host, | |
port = url.Port.ToString(), | |
protocol = url.Scheme, | |
path = url.LocalPath.SplitPaths(), | |
query = (!HttpUtils.HasRequestBody(verb) | |
? requestParams.Select(x => x.key) | |
.Where(x => !x.StartsWith(":")) | |
.ApplyPropertyTypes(propertyTypes) | |
.Map(x => new PostmanRequestKeyValue { key = x.Key, value = x.Value }) | |
: null)!, | |
variable = (url.Segments.Any(x => x.StartsWith(":")) | |
? url.Segments.Where(x => x.StartsWith(":")) | |
.Map(x => new PostmanRequestKeyValue { key = x.Replace(":", ""), value = "" }) | |
: null)! | |
}, | |
method = verb, | |
body = new PostmanRequestBody { | |
formdata = (HttpUtils.HasRequestBody(verb) | |
? requestParams | |
: null)!, | |
}, | |
header = headers, | |
}, | |
name = GetName(feature, request, op.RequestType, virtualPath), | |
})); | |
} | |
return ret; | |
} | |
public string GetName(PostmanFeature feature, Postman request, Type requestType, string virtualPath) | |
{ | |
var fragments = request.Label ?? feature.DefaultLabelFmt; | |
var sb = StringBuilderCache.Allocate(); | |
foreach (var fragment in fragments) | |
{ | |
var parts = fragment.ToLower().Split(':'); | |
var asEnglish = parts.Length > 1 && parts[1] == "english"; | |
if (parts[0] == "type") | |
{ | |
sb.Append(asEnglish ? requestType.Name.ToEnglish() : requestType.Name); | |
} | |
else if (parts[0] == "route") | |
{ | |
sb.Append(virtualPath); | |
} | |
else | |
{ | |
sb.Append(parts[0]); | |
} | |
} | |
return StringBuilderCache.ReturnAndFree(sb); | |
} | |
} | |
public static class PostmanExtensions | |
{ | |
private static readonly char[] PathDelim = {'/'}; | |
internal static string[] SplitPaths(this string text) => | |
text.Split(PathDelim, StringSplitOptions.RemoveEmptyEntries); | |
public static string ToPostmanPathVariables(this string path) | |
{ | |
return path.Replace("{", ":").Replace("}", "").TrimEnd('*'); | |
} | |
public static string AsFriendlyName(this Type type, PostmanFeature feature) | |
{ | |
var parts = type.Name.SplitOnFirst('`'); | |
var typeName = parts[0].LeftPart('['); | |
var suffix = ""; | |
var nullableType = Nullable.GetUnderlyingType(type); | |
if (nullableType != null) | |
{ | |
typeName = nullableType.Name; | |
suffix = "?"; | |
} | |
else if (type.IsArray) | |
{ | |
suffix = "[]"; | |
} | |
else if (type.IsGenericType) | |
{ | |
var args = type.GetGenericArguments().Map(x => | |
x.AsFriendlyName(feature)); | |
suffix = $"<{string.Join(",", args.ToArray())}>"; | |
} | |
return feature.FriendlyTypeNames.TryGetValue(typeName, out var friendlyName) | |
? friendlyName + suffix | |
: typeName + suffix; | |
} | |
public static List<PostmanData> ApplyPropertyTypes(this List<PostmanData> data, | |
Dictionary<string, string> typeMap, string defaultValue = "") | |
{ | |
data.Each(x => x.value = typeMap.TryGetValue(x.key, out var typeName) ? typeName : x.value ?? defaultValue); | |
return data; | |
} | |
public static Dictionary<string, string> ApplyPropertyTypes(this IEnumerable<string> names, | |
Dictionary<string, string> typeMap, | |
string defaultValue = "") | |
{ | |
var to = new Dictionary<string, string>(); | |
names.Each(x => to[x] = typeMap.TryGetValue(x, out var typeName) ? typeName : defaultValue); | |
return to; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment