Compare commits

...

11 Commits

12 changed files with 199 additions and 67 deletions

View File

@ -7,7 +7,7 @@ on:
env: env:
PROJECT_PATH: 'BlazorOpenApi/BlazorOpenApi.csproj' PROJECT_PATH: 'BlazorOpenApi/BlazorOpenApi.csproj'
PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}\output PACKAGE_OUTPUT_DIRECTORY: ${{ github.workspace }}/output
NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json' NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json'
jobs: jobs:
@ -34,7 +34,7 @@ jobs:
uses: battila7/get-version-action@v2 uses: battila7/get-version-action@v2
- name: 'Pack project' - name: 'Pack project'
run: dotnet pack ${{ env.PROJECT_PATH }} --no-restore --no-build --configuration Release --include-symbols -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} run: dotnet pack ${{ env.PROJECT_PATH }} --no-restore --no-build --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }}
- name: 'Push package' - name: 'Push package'
run: dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}\*.nupkg -k ${{ secrets.NUGET_AUTH_TOKEN }} -s ${{ env.NUGET_SOURCE_URL }} run: dotnet nuget push ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/*.nupkg -k ${{ secrets.NUGET_AUTH_TOKEN }} -s ${{ env.NUGET_SOURCE_URL }}

View File

@ -10,6 +10,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
LICENSE.txt = LICENSE.txt LICENSE.txt = LICENSE.txt
.github\workflows\nuget.yml = .github\workflows\nuget.yml
README.md = README.md README.md = README.md
EndProjectSection EndProjectSection
EndProject EndProject

View File

@ -1,14 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Razor"> <Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup> <PropertyGroup>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<Authors>Alexander Shabarshov</Authors> <Authors>Alexander Shabarshov</Authors>
<Description> <Description>
OpenAPI documentation generator for Blazor applications. OpenAPI documentation generator for Blazor applications.
</Description> </Description>
<PackageProjectUrl>https://github.com/unclshura/BlazorOpenApi</PackageProjectUrl> <PackageProjectUrl>https://github.com/unclshura/BlazorOpenApi</PackageProjectUrl>
<PackageTags>openapi, c#, blazor, api, ui, rest, web</PackageTags> <PackageTags>openapi, c#, blazor, api, ui, rest, web</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Markdig" /> <PackageReference Include="Markdig" />
@ -16,4 +17,9 @@
<PackageReference Include="System.Text.Json" /> <PackageReference Include="System.Text.Json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath="\"/>
<None Include="../docs/**" Pack="true" PackagePath="\docs\"/>
</ItemGroup>
</Project> </Project>

View File

@ -7,7 +7,7 @@
@foreach (var (name, val) in Value.Schemas) @foreach (var (name, val) in Value.Schemas)
{ {
<h4>@name</h4> <h4>@name</h4>
<SchemaControl Value="@val"/> <SchemaControl Value="@val" Collapsed="false"/>
} }
</TocMember> </TocMember>
} }

View File

@ -54,7 +54,9 @@
} }
else else
{ {
exampleData[parameter.Name] = GenerateExampleFromSchema(parameter.Schema); var ex = GenerateExampleFromSchema(parameter.Schema);
if (ex != null)
exampleData[parameter.Name] = ex;
} }
} }

View File

@ -5,9 +5,24 @@
@if (Value.Type == "array") @if (Value.Type == "array")
{ {
<Expander HeaderClass="s-type" Class="s-bg-odd" Title="@ArrayText" Collapsed="@Collapsed"> <Expander HeaderClass="s-type" Class="s-bg-odd" Title="@ArrayText" Collapsed="@Collapsed">
<div class="s-nested"> @if (Value.Items?.Type == "object" && SchemaControl.Resolve(Value.Items, Api)?.Properties != null)
<SchemaControl Value="@Value.Items" /> {
</div> @* Skip one level *@
<div class="s-props s-nested">
<table class="schema">
@foreach (var p in SchemaControl.Resolve(Value.Items, Api)!.Properties)
{
<SchemaChildControl Value="@p.Value" Title="@p.Key" Required="@IsRequired(p.Key)" />
}
</table>
</div>
}
else
{
<div class="s-nested">
<SchemaControl Value="@Value.Items" />
</div>
}
</Expander> </Expander>
} }
else if (Value.Type == "object") else if (Value.Type == "object")
@ -55,6 +70,8 @@
} }
@code { @code {
[CascadingParameter]
public OpenApiDocument? Api { get; set; }
[Parameter] [Parameter]
public OpenApiSchema? Value { get; set; } public OpenApiSchema? Value { get; set; }
[Parameter] [Parameter]
@ -73,11 +90,15 @@
if (Value?.Type != "array") if (Value?.Type != "array")
return ""; return "";
string arrayType = Value.Items?.Type ?? "";
if ( Value.Items?.Type != "array" && Value.Items?.Type != "object")
arrayType = Value.Items?.Type ?? "array";
var nullable = Value.Nullable ? "?" : ""; var nullable = Value.Nullable ? "?" : "";
if (Value.MinItems != null || Value.MaxItems != null) if (Value.MinItems != null || Value.MaxItems != null)
return $"array{nullable} [{(Value.MinItems == null ? "" : Value.MinItems.Value)}..{(Value.MaxItems == null ? "" : Value.MaxItems.Value)}]"; return $"{arrayType}{nullable} [{(Value.MinItems == null ? "" : Value.MinItems.Value)}..{(Value.MaxItems == null ? "" : Value.MaxItems.Value)}]";
return $"array{nullable} []"; return $"{arrayType}{nullable} []";
} }
} }

View File

@ -19,15 +19,17 @@
[CascadingParameter] [CascadingParameter]
public OpenApiDocument? Api { get; set; } public OpenApiDocument? Api { get; set; }
private OpenApiSchema? ResolvedValue private OpenApiSchema? ResolvedValue => Resolve(Value, Api) ?? Value;
public static OpenApiSchema? Resolve(OpenApiSchema? schema, OpenApiDocument? api)
{ {
get if (schema == null)
return null;
if (schema.Reference != null && api != null)
{ {
if (Api == null || Value?.Reference == null ) if (api.Components.Schemas.TryGetValue(schema.Reference.Id, out var resolved))
return Value; return resolved;
if (!Api.Components.Schemas.TryGetValue(Value.Reference.Id, out var resolved))
return Value;
return resolved;
} }
return schema;
} }
} }

View File

@ -17,29 +17,30 @@
<TableOfContents Tree="@_tree" /> <TableOfContents Tree="@_tree" />
</Expander> </Expander>
<HeaderControl Value="@_api.Info" DownloadUrl="@Url" /> <div class="oa-main-content">
<ServersControl Value="@_api.Servers" /> <HeaderControl Value="@_api.Info" DownloadUrl="@Url" />
@if (_api.Paths?.Count > 0) <ServersControl Value="@_api.Servers" />
{ @if (_api.Paths?.Count > 0)
<TocMember Title="Endpoints"> {
<h2>Endpoints</h2> <TocMember Title="Endpoints">
@foreach (var path in _api.Paths) <h2>Endpoints</h2>
{ @foreach (var path in _api.Paths)
if (!string.IsNullOrWhiteSpace(path.Key) || path.Value != null)
{ {
<PathControl Key="@path.Key" Value="@path.Value" /> if (!string.IsNullOrWhiteSpace(path.Key) || path.Value != null)
{
<PathControl Key="@path.Key" Value="@path.Value" />
}
} }
} </TocMember>
</TocMember> }
} @if (_api.Components != null)
@if (_api.Components != null) {
{ <TocMember Title="Components">
<TocMember Title="Components"> <h2>Components</h2>
<h2>Components</h2> <ComponentsControl Value="@_api.Components" />
<ComponentsControl Value="@_api.Components" /> </TocMember>
</TocMember> }
} </div>
</CascadingValue> </CascadingValue>
</CascadingValue> </CascadingValue>

View File

@ -1,3 +1,70 @@
# Blazor OpenAPI UI # Blazor OpenAPI UI
Blazor implementation of SwaggerUI-like interface. Author: *unclshura*
This is a Blazor implementation of a SwaggerUI-like interface. It allows you to view your OpenAPI
specifications in a user-friendly way. The motication of this project is to provide a Blazor component that can be used in Blazor applications
to display OpenAPI specifications. Unlike SwaggerUI, this project does not require any JavaScript dependencies.
It is a pure Blazor implementation.
## Installation
You can install the package from NuGet:
```bash
dotnet add package BlazorOpenApi
```
Source code:
```bash
Github: https://github.com/unclshura/BlazorOpenApi
HTTPS: https://github.com/unclshura/BlazorOpenApi.git
SSH: git@github.com:unclshura/BlazorOpenApi.git
```
## Features
- View OpenAPI specifications in a user-friendly way
- Dark and light themes
- Fully customizable color palette
- Separate CSS styles for every element
- Examples generation
- Pure Blazor implementation
## Usage
To use the component, add the following line to your `_Imports.razor` file:
```razor
@using BlazorOpenApi
@using BlazorOpenApi.Controls
```
Then, you can use the component in your Blazor application:
```razor
<OpenAPIUIControl Url="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml" />
```
To customize the palette you can use something like this:
```razor
<OpenAPIUIControl Url="@Url" Palette="@TestPalette"/>
@code {
[Parameter]
[SupplyParameterFromQuery(Name = "url")]
public string Url { get; set; } = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json";
private OpenApiUiPalette TestPalette
{
get
{
var p = new OpenApiUiPalette().Clone();
p.Foreground[7] = "blue";
return p;
}
}
}
```
The demo application is available in the `Demo` folder - https://github.com/unclshura/BlazorOpenApi/tree/master/Demo.
# LICENSE
MIT

View File

@ -9,7 +9,7 @@ internal class TableOfContentsTree : ITableOfContentsTree
private readonly Dictionary<string, TocTreeNode> _nodes = new(); private readonly Dictionary<string, TocTreeNode> _nodes = new();
private readonly List<string> _order = new(); private readonly List<string> _order = new();
public event EventHandler Changed; public event EventHandler? Changed;
public void Clear() public void Clear()
{ {

View File

@ -162,7 +162,7 @@
height: fit-content; height: fit-content;
width: fit-content; width: fit-content;
margin-right: 5px; margin-right: 5px;
border-radius: 5px; border-radius: 3px;
margin-top: 3px; margin-top: 3px;
padding-left: 3px; padding-left: 3px;
padding-right: 3px; padding-right: 3px;
@ -314,16 +314,30 @@
.openapi-ui h1 { .openapi-ui h1 {
margin-top: 24px; margin-top: 24px;
border-left-width: 5px;
border-color: var(--oa-bg-7);
padding-left: 5px;
margin-bottom: 3px;
} }
.openapi-ui h2 { .openapi-ui h2 {
margin-top: 18px; margin-top: 18px;
margin-bottom: 3px;
color: var(--oa-fg-7);
border-bottom-width: 4px;
border-color: var(--oa-bg-7);
} }
.openapi-ui h3 { .openapi-ui h3 {
margin-top: 12px; margin-top: 12px;
margin-bottom: 3px;
border-bottom-width: 1px;
border-color: var(--oa-bg-7);
} }
.openapi-ui h4 { .openapi-ui h4 {
margin-top: 8px; margin-top: 8px;
margin-bottom: 3px;
border-bottom-width: 1px;
border-color: var(--oa-bg-1);
} }
.openapi-ui .tooltip { .openapi-ui .tooltip {
@ -332,6 +346,16 @@
border-bottom: 1px dotted black; border-bottom: 1px dotted black;
} }
.openapi-ui a {
text-decoration: none;
cursor: pointer;
color: var(--oa-fg-link);
}
.openapi-ui a:hover {
color: var(--oa-fg-link-hover);
}
.openapi-ui .tooltip .tooltiptext { .openapi-ui .tooltip .tooltiptext {
visibility: hidden; visibility: hidden;
width: fit-content; width: fit-content;
@ -375,16 +399,17 @@
vertical-align: top; vertical-align: top;
} }
.openapi-ui { .openapi-ui {
--oa-bg-lighter: #0000001f; --oa-bg-lighter: #0000001f;
--oa-bg-darker: #00000055; --oa-bg-darker: #00000055;
--oa-bg-darkest: #000000AA; --oa-bg-darkest: #000000AA;
--oa-fg-normal: #e4e4e4; --oa-fg-normal: #e4e4e4;
--oa-fg-lighter: #ffffff; --oa-fg-lighter: #ffffff;
--oa-fg-darker: #A0A0A0; --oa-fg-darker: #A0A0A0;
--oa-fg-darkest: #808080; --oa-fg-darkest: #808080;
--oa-fg-link: var(--oa-fg-1);
--oa-fg-link-hover: var(--oa-bg-1);
--oa-bg-1: #3655a3; --oa-bg-1: #3655a3;
--oa-bg-2: #7a7620; --oa-bg-2: #7a7620;
--oa-bg-3: #9f4b1e; --oa-bg-3: #9f4b1e;
@ -393,7 +418,6 @@
--oa-bg-6: #851192; --oa-bg-6: #851192;
--oa-bg-7: #266b67; --oa-bg-7: #266b67;
--oa-bg-8: #456f2b; --oa-bg-8: #456f2b;
--oa-fg-1: #7d98de; --oa-fg-1: #7d98de;
--oa-fg-2: #ded971; --oa-fg-2: #ded971;
--oa-fg-3: #eda279; --oa-fg-3: #eda279;
@ -402,10 +426,8 @@
--oa-fg-6: #e571f2; --oa-fg-6: #e571f2;
--oa-fg-7: #98e6e1; --oa-fg-7: #98e6e1;
--oa-fg-8: #54b319; --oa-fg-8: #54b319;
--oa-bg-tag: var(--oa-bg-1); --oa-bg-tag: var(--oa-bg-1);
--oa-fg-tag: var(--oa-fg-lighter); --oa-fg-tag: var(--oa-fg-lighter);
--oa-bg-op-get: var(--oa-bg-1); --oa-bg-op-get: var(--oa-bg-1);
--oa-bg-op-put: var(--oa-bg-2); --oa-bg-op-put: var(--oa-bg-2);
--oa-bg-op-post: var(--oa-bg-3); --oa-bg-op-post: var(--oa-bg-3);
@ -414,20 +436,17 @@
--oa-bg-op-head: var(--oa-bg-6); --oa-bg-op-head: var(--oa-bg-6);
--oa-bg-op-patch: var(--oa-bg-7); --oa-bg-op-patch: var(--oa-bg-7);
--oa-bg-op-trace: var(--oa-bg-8); --oa-bg-op-trace: var(--oa-bg-8);
--oa-bg-header: var(--oa-bg-6); --oa-bg-header: var(--oa-bg-6);
--oa-bg-form: var(--oa-bg-7); --oa-bg-form: var(--oa-bg-7);
--oa-bg-path: var(--oa-bg-8); --oa-bg-path: var(--oa-bg-8);
--oa-bg-cookie: var(--oa-bg-1); --oa-bg-cookie: var(--oa-bg-1);
--oa-bg-query: var(--oa-bg-2); --oa-bg-query: var(--oa-bg-2);
--oa-bg-matrix: var(--oa-bg-6); --oa-bg-matrix: var(--oa-bg-6);
--oa-bg-label: var(--oa-bg-7); --oa-bg-label: var(--oa-bg-7);
--oa-bg-simple: var(--oa-bg-8); --oa-bg-simple: var(--oa-bg-8);
--oa-bg-spacedelimited: var(--oa-bg-1); --oa-bg-spacedelimited: var(--oa-bg-1);
--oa-bg-pipedelimited: var(--oa-bg-2); --oa-bg-pipedelimited: var(--oa-bg-2);
--oa-bg-deepobject: var(--oa-bg-3); --oa-bg-deepobject: var(--oa-bg-3);
--oa-font-small: 12px; --oa-font-small: 12px;
--oa-font-smaller: 10px; --oa-font-smaller: 10px;
--oa-font-large: 16px; --oa-font-large: 16px;

View File

@ -1,5 +1,7 @@
# Blazor OpenAPI UI # Blazor OpenAPI UI
Author: *unclshura*
This is a Blazor implementation of a SwaggerUI-like interface. It allows you to view your OpenAPI This is a Blazor implementation of a SwaggerUI-like interface. It allows you to view your OpenAPI
specifications in a user-friendly way. The motication of this project is to provide a Blazor component that can be used in Blazor applications specifications in a user-friendly way. The motication of this project is to provide a Blazor component that can be used in Blazor applications
to display OpenAPI specifications. Unlike SwaggerUI, this project does not require any JavaScript dependencies. to display OpenAPI specifications. Unlike SwaggerUI, this project does not require any JavaScript dependencies.
@ -7,9 +9,17 @@ It is a pure Blazor implementation.
## Screenshots ## Screenshots
[Light mode](docs/light.png) Light theme:
[Dark mode](docs/dark.png)
[Examples generation](docs/example-data.png)] ![Light mode](https://raw.githubusercontent.com/unclshura/BlazorOpenApi/master/docs/light.png)
Dark Theme:
![Dark mode](https://raw.githubusercontent.com/unclshura/BlazorOpenApi/master/docs/dark.png)
Examples generation:
![Examples generation](https://raw.githubusercontent.com/unclshura/BlazorOpenApi/master/docs/example-data.png)
## Installation ## Installation
@ -18,12 +28,15 @@ You can install the package from NuGet:
dotnet add package BlazorOpenApi dotnet add package BlazorOpenApi
``` ```
Source code: ## Source code
```bash
Github: https://github.com/unclshura/BlazorOpenApi |What |Where |
HTTPS: https://github.com/unclshura/BlazorOpenApi.git |--------|------------------------------------------------|
SSH: git@github.com:unclshura/BlazorOpenApi.git | Github | https://github.com/unclshura/BlazorOpenApi |
``` | HTTPS | https://github.com/unclshura/BlazorOpenApi.git |
| SSH | git@github.com:unclshura/BlazorOpenApi.git |
| NuGet | https://www.nuget.org/packages/BlazorOpenApi/ |
## Features ## Features
@ -42,12 +55,12 @@ To use the component, add the following line to your `_Imports.razor` file:
@using BlazorOpenApi.Controls @using BlazorOpenApi.Controls
``` ```
Then, you can use the component in your Blazor application: Then, you can use the component in your Blazor application:
```razor ```c#
<OpenAPIUIControl Url="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml" /> <OpenAPIUIControl Url="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml" />
``` ```
To customize the palette you can use something like this: To customize the palette you can use something like this:
```razor ```c#
<OpenAPIUIControl Url="@Url" Palette="@TestPalette"/> <OpenAPIUIControl Url="@Url" Palette="@TestPalette"/>
@code { @code {