commit 1744a7829dfac5db76ffcddd05489b924489ad3e Author: Alexander Shabarshov Date: Tue Feb 25 09:04:27 2025 +0000 Add project files. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5be2808 --- /dev/null +++ b/.gitignore @@ -0,0 +1,365 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +*.patch diff --git a/BlazorOpenApi.sln b/BlazorOpenApi.sln new file mode 100644 index 0000000..25210e5 --- /dev/null +++ b/BlazorOpenApi.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34511.84 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorOpenApi.Demo", "Demo\BlazorOpenApi.Demo.csproj", "{23C0112B-477D-40F2-8088-0C086D5E7A7A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorOpenApi", "BlazorOpenApi\BlazorOpenApi.csproj", "{57AD9505-4132-4D79-BF67-03E9F5FC0483}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {23C0112B-477D-40F2-8088-0C086D5E7A7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23C0112B-477D-40F2-8088-0C086D5E7A7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23C0112B-477D-40F2-8088-0C086D5E7A7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23C0112B-477D-40F2-8088-0C086D5E7A7A}.Release|Any CPU.Build.0 = Release|Any CPU + {57AD9505-4132-4D79-BF67-03E9F5FC0483}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57AD9505-4132-4D79-BF67-03E9F5FC0483}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57AD9505-4132-4D79-BF67-03E9F5FC0483}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57AD9505-4132-4D79-BF67-03E9F5FC0483}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3E7BCFDA-CFF3-4DA3-9E81-1BA3F79B0247} + EndGlobalSection +EndGlobal diff --git a/BlazorOpenApi/ApiUtils.cs b/BlazorOpenApi/ApiUtils.cs new file mode 100644 index 0000000..50cc494 --- /dev/null +++ b/BlazorOpenApi/ApiUtils.cs @@ -0,0 +1,19 @@ +using Markdig; +using Microsoft.AspNetCore.Components; + +namespace BlazorOpenApi; + +internal static class ApiUtils +{ + public static MarkupString MarkdownToHtml(string? md) + { + if (string.IsNullOrWhiteSpace(md)) + return new(); + + var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); + var html = Markdown.ToHtml(md, pipeline).ToString() ?? ""; + + return new (html); + } + +} diff --git a/BlazorOpenApi/BlazorOpenApi.csproj b/BlazorOpenApi/BlazorOpenApi.csproj new file mode 100644 index 0000000..f960120 --- /dev/null +++ b/BlazorOpenApi/BlazorOpenApi.csproj @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/BlazorOpenApi/Controls/AnyControl.razor b/BlazorOpenApi/Controls/AnyControl.razor new file mode 100644 index 0000000..5e69093 --- /dev/null +++ b/BlazorOpenApi/Controls/AnyControl.razor @@ -0,0 +1,46 @@ +@using Microsoft.OpenApi.Any + +@if (Value != null) +{ +
+ @switch (Value.AnyType) + { + case AnyType.Null: + break; + case AnyType.Primitive: + { + @AsPrimitive?.PrimitiveType + } + break; + case AnyType.Array: + { + [ + @foreach (var element in AsArray!) + { + + } + ] + } + break; + case AnyType.Object: + { + @foreach (var element in AsObject!) + { +
@element.Key
+ + } + } + break; + } +
+} + +@code { + [Parameter] + public IOpenApiAny? Value { get; set; } + + private IOpenApiPrimitive? AsPrimitive => Value as IOpenApiPrimitive; + private OpenApiArray? AsArray => Value as OpenApiArray; + private OpenApiObject? AsObject => Value as OpenApiObject; + +} diff --git a/BlazorOpenApi/Controls/ComponentsControl.razor b/BlazorOpenApi/Controls/ComponentsControl.razor new file mode 100644 index 0000000..fd4a6ea --- /dev/null +++ b/BlazorOpenApi/Controls/ComponentsControl.razor @@ -0,0 +1,42 @@ +@using Microsoft.OpenApi.Models +@if ( Value != null ) +{ + @if (Value.Schemas?.Count > 0) + { +

Schemas

+ @foreach (var (name, val) in Value.Schemas) + { +

@name

+ + } + } + + + + @if (Value.Parameters?.Count > 0) + { +

Parameters

+ @foreach (var (name, val) in Value.Parameters) + { +

@name

+ + } + } + + @if (Value.Examples?.Count > 0) + { +

Examples

+ @foreach (var (name, val) in Value.Examples) + { + + } + } +} + +@code { + [Parameter] + public OpenApiComponents? Value { get; set; } + + private IList ToList(OpenApiParameter val) + => new List() { val }; +} \ No newline at end of file diff --git a/BlazorOpenApi/Controls/DiscriminatorControl.razor b/BlazorOpenApi/Controls/DiscriminatorControl.razor new file mode 100644 index 0000000..74f8cff --- /dev/null +++ b/BlazorOpenApi/Controls/DiscriminatorControl.razor @@ -0,0 +1,30 @@ +@using Microsoft.OpenApi.Models + +@if (Value != null) +{ +
+ + + + + + + + + + @foreach (var item in Value.Mapping) + { + + + + + } + +
FromTo
@item.Key@item.Value
+
+} + +@code { + [Parameter] + public OpenApiDiscriminator? Value { get; set; } +} diff --git a/BlazorOpenApi/Controls/ExampleControl.razor b/BlazorOpenApi/Controls/ExampleControl.razor new file mode 100644 index 0000000..0ef8f76 --- /dev/null +++ b/BlazorOpenApi/Controls/ExampleControl.razor @@ -0,0 +1,46 @@ +@using Microsoft.OpenApi.Any +@using Microsoft.OpenApi.Models + +@if (Value != null || Example != null ) +{ +
+ @if (!string.IsNullOrWhiteSpace(Name)) + { + @Name + @if (!string.IsNullOrWhiteSpace(Example?.Summary)) + { + - + } + } + @if ( !string.IsNullOrWhiteSpace(Example?.Summary) ) + { + @Example.Summary + } +
+ + if (Example != null) + { + + } +} + +@code { + [Parameter] + public IOpenApiAny? Value { get; set; } + [Parameter] + public string? Name { get; set; } + + [Parameter] + public OpenApiExample? Example { get; set; } + + public IOpenApiAny? Ex => Example != null ? Example.Value : Value; +/* + @if (Ex != null) + { +
+ +
+ } + + */ +} diff --git a/BlazorOpenApi/Controls/Expander.razor b/BlazorOpenApi/Controls/Expander.razor new file mode 100644 index 0000000..2390a02 --- /dev/null +++ b/BlazorOpenApi/Controls/Expander.razor @@ -0,0 +1,43 @@ +
+
+ @Title +
+ @if (Collapsed) + { + + + + } + else + { + + + + } +
+
+ + @if (!Collapsed && ChildContent != null) + { + @ChildContent + } +
+ +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + [Parameter] + public bool Collapsed { get; set; } = true; + [Parameter] + public string Title { get; set; } = ""; + [Parameter] + public string HeaderClass { get; set; } = ""; + [Parameter] + public string Class { get; set; } = ""; + + void Toggle() + { + Collapsed = !Collapsed; + StateHasChanged(); + } +} \ No newline at end of file diff --git a/BlazorOpenApi/Controls/HeaderControl.razor b/BlazorOpenApi/Controls/HeaderControl.razor new file mode 100644 index 0000000..a6c6e0a --- /dev/null +++ b/BlazorOpenApi/Controls/HeaderControl.razor @@ -0,0 +1,49 @@ +@using System.Text.Json +@using Microsoft.OpenApi.Models +@using Microsoft.OpenApi.Readers + +@if (Value != null) +{ +
+

@Value.Title @Value.Version

+
+ @if (!string.IsNullOrWhiteSpace(Value.Contact?.Name)) + { +
+ @Value.Contact.Name +
+ } + @if (Value.Contact?.Url != null) + { +
+ @Value.Contact.Url +
+ } + @if (Value.Contact?.Email != null) + { +
+ @Value.Contact.Email +
+ } +
+ @if (!string.IsNullOrWhiteSpace(DownloadUrl)) + { + @DownloadUrl + } +
+ + @if (!string.IsNullOrWhiteSpace(Value.Description)) + { +

Description

+ + } +} + +@code { + [Parameter] + public OpenApiInfo? Value { get; set; } + + [Parameter] + public string DownloadUrl { get; set; } = ""; + +} diff --git a/BlazorOpenApi/Controls/IndicatorControl.razor b/BlazorOpenApi/Controls/IndicatorControl.razor new file mode 100644 index 0000000..7519513 --- /dev/null +++ b/BlazorOpenApi/Controls/IndicatorControl.razor @@ -0,0 +1,64 @@ +@if (Value) +{ + + @Name + +} + +@code { + [Parameter] + public string Name { get; set; } = ""; + [Parameter] + public string TooltipText { get; set; } = ""; + [Parameter] + public bool Value { get; set; } + [Parameter] + public string Class { get; set; } = ""; + + private string RealClass => $"indicator {Class}"; + + private string? TtText => !string.IsNullOrWhiteSpace(TooltipText) ? "" : Name.ToLower() switch + { + "explode" => ExplodeTooltip, + "deprecated" => DeprecatedTooltip, + "required" => RequiredTooltip, + "allowemptyvalue" => AllowEmptyValueTooltip, + "allowreserved" => AllowReservedTooltip, + _ => "" + }; + + internal const string ExplodeTooltip = + @" +When this is true, parameter values of type array or object generate separate parameters +for each value of the array or key-value pair of the map. +For other types of parameters this property has no effect. +When style is form, the default value is true. +For all other styles, the default value is false. +"; + internal const string DeprecatedTooltip = + @" +Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. +"; + internal const string RequiredTooltip = + @" +Determines whether this parameter is mandatory. +If the parameter location is ""path"", this property is REQUIRED and its value MUST be true. +Otherwise, the property MAY be included and its default value is false. +"; + internal const string AllowEmptyValueTooltip = + @" +Sets the ability to pass empty-valued parameters. +This is valid only for query parameters and allows sending a parameter with an empty value. +Default value is false. +If style is used, and if behavior is n/a (cannot be serialized), +the value of allowEmptyValue SHALL be ignored. +"; + internal const string AllowReservedTooltip = + @" +Determines whether the parameter value SHOULD allow reserved characters, +as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included without percent-encoding. +This property only applies to parameters with an in value of query. +The default value is false. +"; + +} diff --git a/BlazorOpenApi/Controls/MarkdownControl.razor b/BlazorOpenApi/Controls/MarkdownControl.razor new file mode 100644 index 0000000..212b7bc --- /dev/null +++ b/BlazorOpenApi/Controls/MarkdownControl.razor @@ -0,0 +1,9 @@ +@if (!string.IsNullOrWhiteSpace(Value)) +{ +
@ApiUtils.MarkdownToHtml(Value)
+} + +@code { + [Parameter] + public string? Value { get; set; } +} diff --git a/BlazorOpenApi/Controls/OperationControl.razor b/BlazorOpenApi/Controls/OperationControl.razor new file mode 100644 index 0000000..bbfb371 --- /dev/null +++ b/BlazorOpenApi/Controls/OperationControl.razor @@ -0,0 +1,75 @@ +@using System.Text.Json +@using Microsoft.OpenApi.Models +@using Microsoft.OpenApi.Readers + +@if (Value != null) +{ +
+
@Operation
+
-
+
@Endpoint
+
-
+
@Value.Summary
+ @if (Value.Tags.Count > 0) + { +
+ @foreach (var tag in Value.Tags) + { + + + + } +
+ } + @if (_expanded) + { + + + + } + else + { + + + + } + +
+ @if ( _expanded ) + { +
+ + @if (Value != null) + { + + + if (Value.Servers?.Count > 0) + { + + } + + + + } +
+ } +} + +@code { + [Parameter] + public OperationType Operation { get; set; } + [Parameter] + public OpenApiOperation? Value { get; set; } + [Parameter] + public string Endpoint { get; set; } = ""; + + private string OperationClass => $"op-{Operation.ToString().ToLower()}"; + + private bool _expanded = true; + + private void Expand() + { + _expanded = !_expanded; + StateHasChanged(); + } +} diff --git a/BlazorOpenApi/Controls/ParameterControl.razor b/BlazorOpenApi/Controls/ParameterControl.razor new file mode 100644 index 0000000..bb98c51 --- /dev/null +++ b/BlazorOpenApi/Controls/ParameterControl.razor @@ -0,0 +1,59 @@ +@using System.Text.Json +@using Microsoft.OpenApi.Models +@using Microsoft.OpenApi.Readers + +@if (Value != null) +{ + + + + + +
+
@Value.Name
+
+ + +
+
@Value.In
+
+ + +
+ +
+ + +
+
+ + + + + +
+
+ + +
+ + + @if (Value.Examples.Count > 0) + { + foreach (var (n, v) in Value.Examples) + { + + } + } +
+ + +} + +@code { + [Parameter] + public OpenApiParameter? Value { get; set; } + + private string InClass(OpenApiParameter p) => Value == null ? "" : $"p-in-{p?.In?.ToString()?.ToLower()}"; + +} diff --git a/BlazorOpenApi/Controls/ParameterStyleControl.razor b/BlazorOpenApi/Controls/ParameterStyleControl.razor new file mode 100644 index 0000000..9d37f68 --- /dev/null +++ b/BlazorOpenApi/Controls/ParameterStyleControl.razor @@ -0,0 +1,13 @@ +@using Microsoft.OpenApi.Models + +@if ( Value != null ) +{ +
@Value
+} + +@code { + [Parameter] + public ParameterStyle? Value { get; set; } + + private string StyleClass => Value == null ? "" : $"p-style-{Value.ToString()?.ToLower()}"; +} diff --git a/BlazorOpenApi/Controls/ParametersControl.razor b/BlazorOpenApi/Controls/ParametersControl.razor new file mode 100644 index 0000000..7349b63 --- /dev/null +++ b/BlazorOpenApi/Controls/ParametersControl.razor @@ -0,0 +1,34 @@ +@using System.Text.Json +@using Microsoft.OpenApi.Models +@using Microsoft.OpenApi.Readers + +@if (Value?.Count > 0) +{ + + @foreach (var p in Value) + { + + } +
+} + +@code { + [Parameter] + public IList? Value { get; set; } + + [CascadingParameter] + public OpenApiDocument? Api { get; set; } + + private OpenApiParameter? GetResolvedReference(OpenApiParameter p) + { + if (p == null) + return null; + if (!p.UnresolvedReference || p.Reference == null || Api == null) + return p; + + if (!Api.Components.Parameters.TryGetValue(p.Reference.Id, out var resolved)) + return p; + + return resolved; + } +} diff --git a/BlazorOpenApi/Controls/PathControl.razor b/BlazorOpenApi/Controls/PathControl.razor new file mode 100644 index 0000000..08548be --- /dev/null +++ b/BlazorOpenApi/Controls/PathControl.razor @@ -0,0 +1,44 @@ +@using System.Text.Json +@using Microsoft.OpenApi.Models +@using Microsoft.OpenApi.Readers + +@if (!string.IsNullOrWhiteSpace(Key) || Value != null) +{ + @if (!string.IsNullOrWhiteSpace(@Value?.Summary) || !string.IsNullOrWhiteSpace(Value?.Description)) + { +
+

@Key

+
-
+
@Value?.Summary
+
+ } + +
+ @if (Value != null) + { + + + if (Value.Servers?.Count > 0) + { + + } + + @if (Value.Operations?.Count > 0) + { +
+ @foreach (var (key, op) in @Value.Operations.Where(x => x.Value != null)) + { + + } +
+ } + } +
+} + +@code { + [Parameter] + public string? Key { get; set; } + [Parameter] + public OpenApiPathItem? Value { get; set; } +} diff --git a/BlazorOpenApi/Controls/ResponseControl.razor b/BlazorOpenApi/Controls/ResponseControl.razor new file mode 100644 index 0000000..731ae8e --- /dev/null +++ b/BlazorOpenApi/Controls/ResponseControl.razor @@ -0,0 +1,121 @@ +@using Microsoft.OpenApi.Models + +@if (ResolvedValue != null) +{ +
+ + + @if (ResolvedValue.Headers.Count > 0) + { + + @foreach (var (key, val) in ResolvedValue.Headers) + { + + + + + } +
@key +
+
+ +
+ + + + + +
+
+ +
+ + + @if (val.Content.Count > 0) + { + + @foreach (var (k, v) in val.Content) + { + + + + + } +
@k + + @if (v.Encoding.Count > 0) + { + + @foreach (var (ename, eval) in v.Encoding) + { + + + + + + + } +
+
@ename
+
+
@eval.ContentType
+
+ +
+ } + + @foreach (var (ename, eval) in val.Examples) + { + + } +
+ } + + + @if (val.Examples.Count > 0) + { + foreach (var (n, v) in val.Examples) + { + + } + } + +
+ +
+
+ } + @if ( ResolvedValue.Content.Count > 0 ) + { + @foreach (var (key, val) in ResolvedValue.Content) + { +
@key
+
+ +
+ + } + } +
+} + +@code { + [Parameter] + public OpenApiResponse? Value { get; set; } + + [CascadingParameter] + public OpenApiDocument? Api { get; set; } + + private OpenApiResponse? ResolvedValue + { + get + { + if (Value?.Reference == null || Api == null || Value?.UnresolvedReference != true) + return Value; + + if (!Api.Components.Responses.TryGetValue(Value.Reference.Id, out var resolved)) + return Value; + return resolved; + } + } +} \ No newline at end of file diff --git a/BlazorOpenApi/Controls/ResponsesControl.razor b/BlazorOpenApi/Controls/ResponsesControl.razor new file mode 100644 index 0000000..b4e799a --- /dev/null +++ b/BlazorOpenApi/Controls/ResponsesControl.razor @@ -0,0 +1,35 @@ +@using Microsoft.OpenApi.Models + +@if (Value?.Count > 0) +{ +
+
Responces
+ + @foreach (var (name, val) in Value) + { + + + + + } +
@name
+ +
+
+} + +@code { + [Parameter] + public IDictionary? Value { get; set; } + + private static string GetClass(string name) + { + if (name.StartsWith("2")) + return "r-success"; + if (name.StartsWith("4")) + return "r-auth"; + if (name.StartsWith("5")) + return "r-error"; + return ""; + } +} \ No newline at end of file diff --git a/BlazorOpenApi/Controls/SchemaChildControl.razor b/BlazorOpenApi/Controls/SchemaChildControl.razor new file mode 100644 index 0000000..da99099 --- /dev/null +++ b/BlazorOpenApi/Controls/SchemaChildControl.razor @@ -0,0 +1,108 @@ +@using Microsoft.OpenApi.Models + +@if ( Value != null ) +{ + + + @if (Value.Type == "array") + { + +
+ +
+
+ } + else if (Value.Type == "object") + { + +
+ + @foreach (var p in Value.Properties) + { + + } +
+
+
+ } + else + { +
+ +
+ } + + + + + + + + + + + + + @if (Value.AdditionalPropertiesAllowed && Value.AdditionalProperties != null) + { +
+ +
+ } + + + + + +} + +@code { + [Parameter] + public OpenApiSchema? Value { get; set; } + [Parameter] + public string? Title { get; set; } + [Parameter] + public bool Required { get; set; } + + private string TitleText => (string.IsNullOrWhiteSpace(Title) ? Value?.Title : Title) ?? ""; + + private string ArrayText + { + get + { + if (Value?.Type != "array") + return ""; + + var nullable = Value.Nullable ? "?" : ""; + var itemsType = $"{Value.Items?.Type ?? "-string-"}{nullable}"; + + if (Value.MinItems != null || Value.MaxItems != null) + return $"{itemsType} [{(Value.MinItems == null ? "" : Value.MinItems.Value)}..{(Value.MaxItems == null ? "" : Value.MaxItems.Value)}]"; + return $"{itemsType} []"; + } + } + + private string ObjectText + { + get + { + if (Value?.Type != "object") + return ""; + var nullable = Value.Nullable ? "?" : ""; + if (Value.MinProperties != null || Value.MaxProperties != null) + return $"object{nullable} {{{(Value.MinProperties == null ? "" : Value.MinProperties.Value)}..{(Value.MaxProperties == null ? "" : Value.MaxProperties.Value)}}}"; + return $"object{nullable}"; + } + } + + private string TypeText + { + get + { + var nullable = (Value?.Nullable ?? false) ? "?" : ""; + return $"{Value?.Format ?? "string"}{nullable}"; + } + } + + private bool IsRequired(string name) => Value?.Required.Contains(name) ?? false; +} diff --git a/BlazorOpenApi/Controls/SchemaControl.razor b/BlazorOpenApi/Controls/SchemaControl.razor new file mode 100644 index 0000000..177c005 --- /dev/null +++ b/BlazorOpenApi/Controls/SchemaControl.razor @@ -0,0 +1,33 @@ +@using Microsoft.OpenApi.Models + +@if ( ResolvedValue != null ) +{ + + +
+} + + +@code { + [Parameter] + public OpenApiSchema? Value { get; set; } + [Parameter] + public string? Title { get; set; } + [Parameter] + public bool Required { get; set; } + + [CascadingParameter] + public OpenApiDocument? Api { get; set; } + + private OpenApiSchema? ResolvedValue + { + get + { + if (Api == null || Value?.Reference == null ) + return Value; + if (!Api.Components.Schemas.TryGetValue(Value.Reference.Id, out var resolved)) + return Value; + return resolved; + } + } +} diff --git a/BlazorOpenApi/Controls/ServersControl.razor b/BlazorOpenApi/Controls/ServersControl.razor new file mode 100644 index 0000000..d86565b --- /dev/null +++ b/BlazorOpenApi/Controls/ServersControl.razor @@ -0,0 +1,69 @@ +@using System.Text.Json +@using Microsoft.OpenApi.Models +@using Microsoft.OpenApi.Readers + +@if (Value != null && Value.Count > 0) +{ +

Servers

+ + @foreach (var server in Value) + { + + + + + @if (server.Variables?.Count > 0) + { + + + + } + } +
@server.Url
+
+ + + + + + + + + + @foreach (var (key, val) in server.Variables.OrderBy(x => x.Key)) + { + + + + + + + } + +
NameDescriptionValue
@key + + @if (val.Enum?.Count > 0) + { +
    + @foreach (var v in val.Enum) + { + if (string.IsNullOrWhiteSpace(v)) + { +
  • < empty string >
  • + } + else + { +
  • @v
  • + } + } +
+ } +
+
+
+} + +@code { + [Parameter] + public IList? Value { get; set; } +} diff --git a/BlazorOpenApi/Controls/Text.razor b/BlazorOpenApi/Controls/Text.razor new file mode 100644 index 0000000..bef4a31 --- /dev/null +++ b/BlazorOpenApi/Controls/Text.razor @@ -0,0 +1,11 @@ +@if (!string.IsNullOrWhiteSpace(Value)) +{ +
@Value
+} + +@code { + [Parameter] + public string? Value { get; set; } + [Parameter] + public string? Class { get; set; } +} diff --git a/BlazorOpenApi/Controls/Tooltip.razor b/BlazorOpenApi/Controls/Tooltip.razor new file mode 100644 index 0000000..d8141ff --- /dev/null +++ b/BlazorOpenApi/Controls/Tooltip.razor @@ -0,0 +1,24 @@ +@if (string.IsNullOrWhiteSpace(TooltipText)) +{ +
+ @ChildContent +
+} +else +{ +
+
+ @ChildContent +
+
@TooltipText
+
+} + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + [Parameter] + public string? TooltipText { get; set; } + [Parameter] + public string Class { get; set; } = ""; +} diff --git a/BlazorOpenApi/OpenAPIUIControl.razor b/BlazorOpenApi/OpenAPIUIControl.razor new file mode 100644 index 0000000..5d54315 --- /dev/null +++ b/BlazorOpenApi/OpenAPIUIControl.razor @@ -0,0 +1,101 @@ +@using System.Text.Json +@using Microsoft.OpenApi.Models +@using Microsoft.OpenApi.Readers + +@if (Palette != null ) +{ + @PaletteStr +} + +
+ + + + @if (_api.Paths?.Count > 0) + { +

Endpoints

+ foreach (var path in _api.Paths) + { + if (!string.IsNullOrWhiteSpace(path.Key) || path.Value != null) + { + + } + } + } + @if (_api.Components != null) + { +

Components

+ + } +
+
+ +@code { + [Parameter] public string Url { get; set; } = ""; + [Parameter] public string Json { get; set; } = ""; + [Parameter] public OpenApiUiPalette? Palette { get; set; } + + private string _loadedFor = ""; + private OpenApiDocument _api = new(); + + private MarkupString PaletteStr => Palette?.AsMarkupString ?? new(); + + protected override async Task OnParametersSetAsync() + { + var loadedFromUrl = _loadedFor == Url && !string.IsNullOrWhiteSpace(Url); + var loadedFromJson = _loadedFor == Json && !string.IsNullOrWhiteSpace(Json); + if (loadedFromUrl || loadedFromJson) + return; + + if (!string.IsNullOrWhiteSpace(Json)) + LoadFromJson(); + else if (!string.IsNullOrWhiteSpace(Url)) + await LoadFromUrl(); + + } + + private async Task LoadFromUrl() + { + try + { + using var client = new HttpClient(); + + var jsonStream = await client.GetStreamAsync(Url); + var reader = new OpenApiStreamReader(); + var res = await reader.ReadAsync(jsonStream); + + + if (res?.OpenApiDocument == null) + throw new InvalidOperationException($"Can't parse {Url}"); + + _api = res.OpenApiDocument; + _loadedFor = Url; + Json = ""; + } + catch (Exception) + { + _loadedFor = ""; + _api = new(); + } + } + + private void LoadFromJson() + { + try + { + var reader = new OpenApiStringReader(); + var res = reader.Read(Json, out var _) + ?? throw new InvalidOperationException($"Can't parse Json/Yaml"); + + _api = res; + + _loadedFor = Url; + // Url = ""; <-- do not set URL to "" + } + catch (Exception) + { + _loadedFor = ""; + _api = new(); + } + } +} diff --git a/BlazorOpenApi/OpenApiUiPalette.cs b/BlazorOpenApi/OpenApiUiPalette.cs new file mode 100644 index 0000000..0d232b7 --- /dev/null +++ b/BlazorOpenApi/OpenApiUiPalette.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Components; + +namespace BlazorOpenApi; + +public class OpenApiUiPalette : ICloneable +{ + public string BackgroundLighter { get; set; } = "#0000001f"; + public string BackgroundDarker { get; set; } = "#00000055"; + public string BackgroundDarkest { get; set; } = "#000000AA"; + + public string ForegroundNormal { get; set; } = "#e4e4e4"; + public string ForegroundLighter { get; set; } = "#ffffff"; + public string ForegroundDarker { get; set; } = "#A0A0A0"; + public string ForegroundDarkest { get; set; } = "#808080"; + + public string[/*8*/] Background { get; set; } = [ + "#3655a3", + "#7a7620", + "#9f4b1e", + "#7e1b29", + "#52628b", + "#851192", + "#266b67", + "#456f2b" + ]; + + public string[/*8*/] Foreground { get; set; } = [ + "#7d98de", + "#ded971", + "#eda279", + "#e97d8d", + "#b1c0e6", + "#e571f2", + "#98e6e1", + "#54b319" + ]; + + public MarkupString AsMarkupString => new MarkupString( +$@" + +"); + + public OpenApiUiPalette Clone() + { + var other = new OpenApiUiPalette + { + BackgroundLighter = BackgroundLighter, + BackgroundDarker = BackgroundDarker, + BackgroundDarkest = BackgroundDarkest, + + ForegroundNormal = ForegroundNormal, + ForegroundLighter = ForegroundLighter, + ForegroundDarker = ForegroundDarker, + ForegroundDarkest = ForegroundDarkest, + + Background = (string[])Background.Clone(), + Foreground = (string[])Foreground.Clone() + }; + + return other; + } + + object ICloneable.Clone() => Clone(); +} diff --git a/BlazorOpenApi/Properties/launchSettings.json b/BlazorOpenApi/Properties/launchSettings.json new file mode 100644 index 0000000..0887d65 --- /dev/null +++ b/BlazorOpenApi/Properties/launchSettings.json @@ -0,0 +1,26 @@ +{ + "iisSettings": { + "iisExpress": { + "applicationUrl": "http://localhost:57161", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5026", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/BlazorOpenApi/README.md b/BlazorOpenApi/README.md new file mode 100644 index 0000000..cbc66d5 --- /dev/null +++ b/BlazorOpenApi/README.md @@ -0,0 +1,3 @@ +# Blazor OpenAPI UI + +Blazor implementation of SwaggerUI-like interface. diff --git a/BlazorOpenApi/_Imports.razor b/BlazorOpenApi/_Imports.razor new file mode 100644 index 0000000..d15b2d7 --- /dev/null +++ b/BlazorOpenApi/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using BlazorOpenApi +@using BlazorOpenApi.Controls diff --git a/BlazorOpenApi/wwwroot/css/openapi-ui.css b/BlazorOpenApi/wwwroot/css/openapi-ui.css new file mode 100644 index 0000000..227d31c --- /dev/null +++ b/BlazorOpenApi/wwwroot/css/openapi-ui.css @@ -0,0 +1,392 @@ +.descriminator {} + +.example {} +.e-item {} +.e-name {} +.e-title {} + +.expander { + display: flex; + flex-direction: column; +} + +.ex-header { + display: flex; + flex-direction: row; +} + +.header {} +.h-contact {} +.h-email {} +.h-name {} +.h-source-url {} +.h-title {} +.h-url {} +.h-version { + font-size: var(--oa-font-large); + width: fit-content; + color: var(--oa-fg-lighter); + border-radius: 5px; + background-color: var(--oa-bg-tag); + padding-left: 5px; + padding-right: 5px; +} + +.h1 {} +.h2 {} + +.indicator { + color: var(--oa-fg-lighter); + background-color: var(--oa-bg-lighter); + font-size: var(--oa-font-smaller); + height: fit-content; + width: fit-content; + margin-right: 5px; + border-radius: 5px; + margin-top: 3px; + padding-left: 3px; + padding-right: 3px; + text-transform: uppercase; +} + +.i-required { + background-color: var(--oa-bg-3); +} +.i-deprecated { + background-color: var(--oa-bg-4); +} + +.markdown {} + +.operation {} +.operations {} +.op-params {} +.op-header { + display: flex; + width: 100%; + margin-top: 16px; + padding: 5px; + border-radius: 5px; + color: var(--oa-fg-lighter); +} +.op-summary { + width: 100%; +} +.op-tags { + display: flex; +} +.op-tag { + background-color: var(--oa-bg-tag); + color: var(--oa-fg-tag); + border: solid 1px; + margin: 1px; + display: block; + width: fit-content; + padding: 2px; +} +.op-tag-name { + font-size: var(--oa-font-small); + white-space: nowrap; + text-transform: lowercase; +} +.op-type { + border: solid 1px; + margin: 1px; + display: block; + width: fit-content; + padding: 2px; + padding-top: 3px; + font-size: var(--oa-font-small); + text-transform: uppercase; + border-radius: 3px; +} +.op-get { + background-color: var(--oa-bg-op-get); +} +.op-put { + background-color: var(--oa-bg-op-put); +} +.op-post { + background-color: var(--oa-bg-op-post); +} +.op-delete { + background-color: var(--oa-bg-op-delete); +} +.op-options { + background-color: var(--oa-bg-op-options); +} +.op-head { + background-color: var(--oa-bg-op-head); +} +.op-patch { + background-color: var(--oa-bg-op-patch); +} +.op-trace { + background-color: var(--oa-bg-op-trace); +} +.parameter { + margin-left: 24px; +} +.p-attrs { + display: flex; +} +.p-body { + margin-left: 20px; +} +.p-in { + color: var(--oa-fg-lighter); + font-size: var(--oa-font-smaller); + height: fit-content; + width: fit-content; + margin-right: 5px; + border-radius: 5px; + margin-top: 3px; + padding-left: 3px; + padding-right: 3px; +} +.p-in-query { + background-color: var(--oa-bg-query); +} +.p-in-header { + background-color: var(--oa-bg-header); +} +.p-in-path { + background-color: var(--oa-bg-path); +} +.p-in-cookie { + background-color: var(--oa-bg-cookie); +} +.p-indicators { + display: flex; + margin-right: 5px; +} +.p-name { + color: var(--oa-fg-1); + margin-right: 5px; + font-weight: bold; +} +.p-style { + color: var(--oa-fg-lighter); + font-size: var(--oa-font-smaller); + height: fit-content; + width: fit-content; + margin-right: 5px; + border-radius: 5px; + margin-top: 3px; + padding-left: 3px; + padding-right: 3px; +} +.p-style-matrix { + background-color: var(--oa-bg-matrix); +} +.p-style-label { + background-color: var(--oa-bg-label); +} +.p-style-form { + background-color: var(--oa-bg-form); +} +.p-style-simple { + background-color: var(--oa-bg-simple); +} +.p-style-spacedelimited { + background-color: var(--oa-bg-spacedelimited); +} +.p-style-pipedelimited { + background-color: var(--oa-bg-pipedelimited); +} +.p-style-deepobject { + background-color: var(--oa-bg-deepobject); +} + +.path {} +.pa-header { + display: flex; +} +.pa-summary { +} + +.responses {} +.response {} +.r-name { + border: solid 1px; + padding-left: 3px; + padding-right: 3px; + margin-right: 3px; +} +.r-success { + color: var(--oa-fg-8); +} +.r-auth { + color: var(--oa-fg-3); +} +.r-error { + color: var(--oa-fg-4); +} +.r-encoding{} + +.schema {} +.s-additional-props {} +.s-props {} +.s-title { + color: var(--oa-fg-1); + margin-right: 5px; + font-weight: bold; +} +.s-nested { + margin-left: 20px; +} +.s-type { + color: var(--oa-fg-8); +} +.s-bg-odd { + background-color: var(--oa-bg-lighter); +} + +.s-bg-even { + background-color: var(--oa-bg-lighter); +} + +.servers { + display: flex; + flex-direction: column; +} +.sr-server { + display: flex; + flex-direction:row; +} +.sr-default {} +.sr-enum {} +.sr-url { + margin-left: 5px; +} +.sr-var-name {} +.sr-var-val {} +.sr-vars {} +.spacer { + margin-left: 5px; + margin-right: 5px; +} +.op-header-text { + padding-top: 3px; +} + +.openapi-ui h1 { + margin-top: 24px; +} + +.openapi-ui h2 { + margin-top: 18px; +} +.openapi-ui h3 { + margin-top: 12px; +} +.openapi-ui h4 { + margin-top: 8px; +} + +.openapi-ui .tooltip { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; +} + +.openapi-ui .tooltip .tooltiptext { + visibility: hidden; + width: fit-content; + width: 201px; + font-size: var(--oa-font-smaller); + background-color: var(--oa-bg-darkest); + color: var(--oa-fg-darker); + text-align: center; + border-radius: 6px; + padding: 5px 0; + position: absolute; + z-index: 1; + bottom: 125%; + left: 50%; + margin-left: -60px; + opacity: 0; + transition: opacity 0.3s; +} + +.openapi-ui .tooltip .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: #555 transparent transparent transparent; +} + +.openapi-ui .tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.openapi-ui span { + margin-left: 5px; +} + +.openapi-ui td { + vertical-align: top; +} + +.openapi-ui { + --oa-bg-lighter: #0000001f; + --oa-bg-darker: #00000055; + --oa-bg-darkest: #000000AA; + + --oa-fg-normal: #e4e4e4; + --oa-fg-lighter: #ffffff; + --oa-fg-darker: #A0A0A0; + --oa-fg-darkest: #808080; + + --oa-bg-1: #3655a3; + --oa-bg-2: #7a7620; + --oa-bg-3: #9f4b1e; + --oa-bg-4: #7e1b29; + --oa-bg-5: #52628b; + --oa-bg-6: #851192; + --oa-bg-7: #266b67; + --oa-bg-8: #456f2b; + + --oa-fg-1: #7d98de; + --oa-fg-2: #ded971; + --oa-fg-3: #eda279; + --oa-fg-4: #e97d8d; + --oa-fg-5: #b1c0e6; + --oa-fg-6: #e571f2; + --oa-fg-7: #98e6e1; + --oa-fg-8: #54b319; + + --oa-bg-tag: var(--oa-bg-1); + --oa-fg-tag: var(--oa-fg-lighter); + + --oa-bg-op-get: var(--oa-bg-1); + --oa-bg-op-put: var(--oa-bg-2); + --oa-bg-op-post: var(--oa-bg-3); + --oa-bg-op-delete: var(--oa-bg-4); + --oa-bg-op-options: var(--oa-bg-5); + --oa-bg-op-head: var(--oa-bg-6); + --oa-bg-op-patch: var(--oa-bg-7); + --oa-bg-op-trace: var(--oa-bg-8); + + --oa-bg-header: var(--oa-bg-6); + --oa-bg-form: var(--oa-bg-7); + --oa-bg-path: var(--oa-bg-8); + --oa-bg-cookie: var(--oa-bg-1); + --oa-bg-query: var(--oa-bg-2); + + --oa-bg-matrix: var(--oa-bg-6); + --oa-bg-label: var(--oa-bg-7); + --oa-bg-simple: var(--oa-bg-8); + --oa-bg-spacedelimited: var(--oa-bg-1); + --oa-bg-pipedelimited: var(--oa-bg-2); + --oa-bg-deepobject: var(--oa-bg-3); + + --oa-font-small: 12px; + --oa-font-smaller: 10px; + --oa-font-large: 16px; +} diff --git a/Demo/App.razor b/Demo/App.razor new file mode 100644 index 0000000..843f201 --- /dev/null +++ b/Demo/App.razor @@ -0,0 +1,11 @@ + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
\ No newline at end of file diff --git a/Demo/BlazorOpenApi.Demo.csproj b/Demo/BlazorOpenApi.Demo.csproj new file mode 100644 index 0000000..3eaaf49 --- /dev/null +++ b/Demo/BlazorOpenApi.Demo.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Demo/Controllers/TestController.cs b/Demo/Controllers/TestController.cs new file mode 100644 index 0000000..304ce60 --- /dev/null +++ b/Demo/Controllers/TestController.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc; + +// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace BlazorOpenApi.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class TestController : ControllerBase +{ + // GET: api/ + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api//5 + [HttpGet("{id}")] + public string Get(int id) + { + return "value"; + } + + // POST api/ + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api//5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api//5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } +} diff --git a/Demo/Examples/petstore.yaml b/Demo/Examples/petstore.yaml new file mode 100644 index 0000000..f6b39b0 --- /dev/null +++ b/Demo/Examples/petstore.yaml @@ -0,0 +1,1228 @@ +openapi: 3.0.0 +servers: + - url: //petstore.swagger.io/v2 + description: Default server + - url: //petstore.swagger.io/sandbox + description: Sandbox server +info: + description: | + This is a sample server Petstore server. + You can find out more about Swagger at + [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). + For this sample, you can use the api key `special-key` to test the authorization filters. + + # Introduction + This API is documented in **OpenAPI format** and is based on + [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. + It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) + tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard + OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md). + + # OpenAPI Specification + This API is documented in **OpenAPI format** and is based on + [Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. + It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) + tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard + OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md). + + # Cross-Origin Resource Sharing + This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/). + And that allows cross-domain communication from the browser. + All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site. + + # Authentication + + Petstore offers two forms of authentication: + - API Key + - OAuth2 + OAuth2 - an open protocol to allow secure authorization in a simple + and standard method from web, mobile and desktop applications. + + + + version: 1.0.0 + title: Swagger Petstore + termsOfService: 'http://swagger.io/terms/' + contact: + name: API Support + email: apiteam@swagger.io + url: https://github.com/Redocly/redoc + x-logo: + url: 'https://redocly.github.io/redoc/petstore-logo.png' + altText: Petstore logo + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +externalDocs: + description: Find out how to create Github repo for your OpenAPI spec. + url: 'https://github.com/Rebilly/generator-openapi-repo' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user + - name: pet_model + x-displayName: The Pet Model + description: | + + - name: store_model + x-displayName: The Order Model + description: | + +x-tagGroups: + - name: General + tags: + - pet + - store + - name: User Management + tags: + - user + - name: Models + tags: + - pet_model + - store_model +paths: + /pet: + parameters: + - name: Accept-Language + in: header + description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US" + example: en-US + required: false + schema: + type: string + default: en-AU + - name: cookieParam + in: cookie + description: Some cookie + required: true + schema: + type: integer + format: int64 + post: + tags: + - pet + summary: Add a new pet to the store + description: Add new pet to the store inventory. + operationId: addPet + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + x-codeSamples: + - lang: 'C#' + source: | + PetStore.v1.Pet pet = new PetStore.v1.Pet(); + pet.setApiKey("your api key"); + pet.petType = PetStore.v1.Pet.TYPE_DOG; + pet.name = "Rex"; + // set other fields + PetStoreResponse response = pet.create(); + if (response.statusCode == HttpStatusCode.Created) + { + // Successfully created + } + else + { + // Something wrong -- check response for errors + Console.WriteLine(response.getRawResponse()); + } + - lang: PHP + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->create($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + x-codeSamples: + - lang: PHP + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetId(1); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->update($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + requestBody: + $ref: '#/components/requestBodies/Pet' + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + deprecated: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + example: "Bearer " + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + schema: + type: array + minItems: 1 + maxItems: 3 + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + deprecated: true + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: array + maxItems: 999 + items: + maxItems: 111 + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + minProperties: 2 + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + content: + application/json: + example: + status: 400 + message: "Invalid Order" + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + minimum: 1 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /store/subscribe: + post: + tags: + - store + summary: Subscribe to the Store events + description: Add subscription for a store events + requestBody: + content: + application/json: + schema: + type: object + properties: + callbackUrl: + type: string + format: uri + description: This URL will be called by the server when the desired event will occur + example: https://myserver.com/send/callback/here + eventName: + type: string + description: Event name for the subscription + enum: + - orderInProgress + - orderShipped + - orderDelivered + example: orderInProgress + required: + - callbackUrl + - eventName + responses: + '201': + description: Subscription added + content: + application/json: + schema: + type: object + properties: + subscriptionId: + type: string + example: AAA-123-BBB-456 + callbacks: + orderInProgress: + '{$request.body#/callbackUrl}?event={$request.body#/eventName}': + servers: + - url: //callback-url.path-level/v1 + description: Path level server 1 + - url: //callback-url.path-level/v2 + description: Path level server 2 + post: + summary: Order in Progress (Summary) + description: A callback triggered every time an Order is updated status to "inProgress" (Description) + externalDocs: + description: Find out more + url: 'https://more-details.com/demo' + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + status: + type: string + example: 'inProgress' + application/xml: + schema: + type: object + properties: + orderId: + type: string + example: '123' + example: | + + + 123 + inProgress + 2018-10-19T16:46:45Z + + responses: + '200': + description: Callback successfully processed and no retries will be performed + content: + application/json: + schema: + type: object + properties: + someProp: + type: string + example: '123' + '299': + description: Response for cancelling subscription + '500': + description: Callback processing failed and retries will be performed + x-codeSamples: + - lang: 'C#' + source: | + PetStore.v1.Pet pet = new PetStore.v1.Pet(); + pet.setApiKey("your api key"); + pet.petType = PetStore.v1.Pet.TYPE_DOG; + pet.name = "Rex"; + // set other fields + PetStoreResponse response = pet.create(); + if (response.statusCode == HttpStatusCode.Created) + { + // Successfully created + } + else + { + // Something wrong -- check response for errors + Console.WriteLine(response.getRawResponse()); + } + - lang: PHP + source: | + $form = new \PetStore\Entities\Pet(); + $form->setPetType("Dog"); + $form->setName("Rex"); + // set other fields + try { + $pet = $client->pets()->create($form); + } catch (UnprocessableEntityException $e) { + var_dump($e->getErrors()); + } + put: + description: Order in Progress (Only Description) + servers: + - url: //callback-url.operation-level/v1 + description: Operation level server 1 (Operation override) + - url: //callback-url.operation-level/v2 + description: Operation level server 2 (Operation override) + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + status: + type: string + example: 'inProgress' + application/xml: + schema: + type: object + properties: + orderId: + type: string + example: '123' + example: | + + + 123 + inProgress + 2018-10-19T16:46:45Z + + responses: + '200': + description: Callback successfully processed and no retries will be performed + content: + application/json: + schema: + type: object + properties: + someProp: + type: string + example: '123' + orderShipped: + '{$request.body#/callbackUrl}?event={$request.body#/eventName}': + post: + description: | + Very long description + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + culpa qui officia deserunt mollit anim id est laborum. + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + estimatedDeliveryDate: + type: string + format: date-time + example: '2018-11-11T16:00:00Z' + responses: + '200': + description: Callback successfully processed and no retries will be performed + orderDelivered: + 'http://notificationServer.com?url={$request.body#/callbackUrl}&event={$request.body#/eventName}': + post: + deprecated: true + summary: Order delivered + description: A callback triggered every time an Order is delivered to the recipient + requestBody: + content: + application/json: + schema: + type: object + properties: + orderId: + type: string + example: '123' + timestamp: + type: string + format: date-time + example: '2018-10-19T16:46:45Z' + responses: + '200': + description: Callback successfully processed and no retries will be performed + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/json: + schema: + type: string + examples: + response: + value: OK + application/xml: + schema: + type: string + examples: + response: + value: OK + text/plain: + examples: + response: + value: OK + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation +components: + schemas: + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + Cat: + description: A representation of a cat + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + huntingSkill: + type: string + description: The measured skill for hunting + default: lazy + example: adventurous + enum: + - clueless + - lazy + - adventurous + - aggressive + required: + - huntingSkill + Category: + type: object + properties: + id: + description: Category ID + allOf: + - $ref: '#/components/schemas/Id' + name: + description: Category name + type: string + minLength: 1 + sub: + description: Test Sub Category + type: object + properties: + prop1: + type: string + description: Dumb Property + xml: + name: Category + Dog: + description: A representation of a dog + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + packSize: + type: integer + format: int32 + description: The size of the pack the dog is from + default: 1 + minimum: 1 + required: + - packSize + HoneyBee: + description: A representation of a honey bee + allOf: + - $ref: '#/components/schemas/Pet' + - type: object + properties: + honeyPerDay: + type: number + description: Average amount of honey produced per day in ounces + example: 3.14 + multipleOf: .01 + required: + - honeyPerDay + Id: + type: integer + format: int64 + readOnly: true + Order: + type: object + properties: + id: + description: Order ID + allOf: + - $ref: '#/components/schemas/Id' + petId: + description: Pet ID + allOf: + - $ref: '#/components/schemas/Id' + quantity: + type: integer + format: int32 + minimum: 1 + default: 1 + shipDate: + description: Estimated ship date + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + description: Indicates whenever order was completed or not + type: boolean + default: false + readOnly: true + requestId: + description: Unique Request Id + type: string + writeOnly: true + xml: + name: Order + Pet: + type: object + required: + - name + - photoUrls + discriminator: + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + bee: '#/components/schemas/HoneyBee' + properties: + id: + externalDocs: + description: "Find more info here" + url: "https://example.com" + description: Pet ID + allOf: + - $ref: '#/components/schemas/Id' + category: + description: Categories this pet belongs to + allOf: + - $ref: '#/components/schemas/Category' + name: + description: The name given to a pet + type: string + example: Guru + photoUrls: + description: The list of URL to a cute photos featuring pet + type: array + maxItems: 20 + xml: + name: photoUrl + wrapped: true + items: + type: string + format: url + friend: + allOf: + - $ref: '#/components/schemas/Pet' + tags: + description: Tags attached to the pet + type: array + minItems: 1 + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: Pet status in the store + enum: + - available + - pending + - sold + petType: + description: Type of a pet + type: string + xml: + name: Pet + Tag: + type: object + properties: + id: + description: Tag ID + allOf: + - $ref: '#/components/schemas/Id' + name: + description: Tag name + type: string + minLength: 1 + xml: + name: Tag + User: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + pet: + oneOf: + - $ref: '#/components/schemas/Pet' + - $ref: '#/components/schemas/Tag' + username: + description: User supplied username + type: string + minLength: 4 + example: John78 + firstName: + description: User first name + type: string + minLength: 1 + example: John + lastName: + description: User last name + type: string + minLength: 1 + example: Smith + email: + description: User email address + type: string + format: email + example: john.smith@example.com + password: + type: string + description: >- + User password, MUST contain a mix of upper and lower case letters, + as well as digits + format: password + minLength: 8 + pattern: '/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/' + example: drowssaP123 + phone: + description: User phone number in international format + type: string + pattern: '/^\+(?:[0-9]-?){6,14}[0-9]$/' + example: +1-202-555-0192 + userStatus: + description: User status + type: integer + format: int32 + addresses: + type: array + minItems: 0 + maxLength: 10 + items: + - type: object + properties: + city: + type: string + minLength: 0 + country: + type: string + minLength: 0 + street: + description: includes build/apartment number + type: string + minLength: 0 + - type: number + additionalItems: + type: string + xml: + name: User + requestBodies: + Pet: + content: + application/json: + schema: + allOf: + - description: My Pet + title: Pettie + - $ref: '#/components/schemas/Pet' + application/xml: + schema: + type: 'object' + properties: + name: + type: string + description: hooray + description: Pet object that needs to be added to the store + required: true + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + securitySchemes: + petstore_auth: + description: | + Get access to data while protecting your account credentials. + OAuth2 is also a safer and more secure way to give you access. + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + description: > + For this sample, you can use the api key `special-key` to test the + authorization filters. + type: apiKey + name: api_key + in: header + examples: + Order: + value: + quantity: 1 + shipDate: '2018-10-19T16:46:45Z' + status: placed + complete: false +x-webhooks: + newPet: + post: + summary: New pet + description: Information about a new pet in the systems + operationId: newPet + tags: + - pet + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + "200": + description: Return a 200 status to indicate that the data was received successfully \ No newline at end of file diff --git a/Demo/Pages/Error.cshtml b/Demo/Pages/Error.cshtml new file mode 100644 index 0000000..5989800 --- /dev/null +++ b/Demo/Pages/Error.cshtml @@ -0,0 +1,42 @@ +@page +@model BlazorOpenApi.Pages.ErrorModel + + + + + + + + Error + + + + + +
+
+

Error.

+

An error occurred while processing your request.

+ + @if (Model.ShowRequestId) + { +

+ Request ID: @Model.RequestId +

+ } + +

Development Mode

+

+ Swapping to the Development environment displays detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+
+
+ + + diff --git a/Demo/Pages/Error.cshtml.cs b/Demo/Pages/Error.cshtml.cs new file mode 100644 index 0000000..c287efd --- /dev/null +++ b/Demo/Pages/Error.cshtml.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Diagnostics; + +namespace BlazorOpenApi.Pages; + +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + private readonly ILogger _logger; + + public ErrorModel(ILogger logger) + { + _logger = logger; + } + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } +} diff --git a/Demo/Pages/Index.razor b/Demo/Pages/Index.razor new file mode 100644 index 0000000..1b068c0 --- /dev/null +++ b/Demo/Pages/Index.razor @@ -0,0 +1,58 @@ +@page "/" +@inject NavigationManager Navigation + +Index +@* +Hello, world! +Welcome to your new app, powered by MudBlazor! +You can find documentation and examples on our website here: www.mudblazor.com + +/api-docs/index.html +*@ + +@* +