tools/code/common/ApiSpecification.cs (212 lines of code) (raw):
using LanguageExt;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Readers;
using System;
using System.Collections.Immutable;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace common;
public abstract record ApiSpecification
{
public sealed record GraphQl : ApiSpecification;
public sealed record Wadl : ApiSpecification;
public sealed record Wsdl : ApiSpecification;
public sealed record OpenApi : ApiSpecification
{
public required OpenApiVersion Version { get; init; }
public required OpenApiFormat Format { get; init; }
}
}
public abstract record OpenApiVersion
{
public sealed record V2 : OpenApiVersion;
public sealed record V3 : OpenApiVersion;
public static async ValueTask<Option<OpenApiVersion>> TryParse(BinaryData data, CancellationToken cancellationToken)
{
#pragma warning disable CA1031 // Do not catch general exception types
try
{
using var stream = data.ToStream();
var reader = new OpenApiStreamReader();
var result = await reader.ReadAsync(stream, cancellationToken);
return result.OpenApiDiagnostic.SpecificationVersion switch
{
OpenApiSpecVersion.OpenApi2_0 => new OpenApiVersion.V2(),
OpenApiSpecVersion.OpenApi3_0 => new OpenApiVersion.V3(),
_ => Option<OpenApiVersion>.None
};
}
catch (Exception)
{
return Option<OpenApiVersion>.None;
}
#pragma warning restore CA1031 // Do not catch general exception types
}
}
public abstract record OpenApiFormat
{
public sealed record Json : OpenApiFormat;
public sealed record Yaml : OpenApiFormat;
}
public abstract record ApiSpecificationFile : ResourceFile
{
public required ApiDirectory Parent { get; init; }
public abstract ApiSpecification Specification { get; }
public static ApiSpecificationFile From(ApiSpecification specification, ApiName apiName, ManagementServiceDirectory serviceDirectory) =>
specification switch
{
ApiSpecification.GraphQl => GraphQlSpecificationFile.From(apiName, serviceDirectory),
ApiSpecification.Wadl => WadlSpecificationFile.From(apiName, serviceDirectory),
ApiSpecification.Wsdl => WsdlSpecificationFile.From(apiName, serviceDirectory),
ApiSpecification.OpenApi openApi => OpenApiSpecificationFile.From(openApi, apiName, serviceDirectory),
_ => throw new NotImplementedException()
};
public static async ValueTask<Option<ApiSpecificationFile>> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) =>
await TryParse(file,
getFileContents: async file => await file.ReadAsBinaryData(cancellationToken),
serviceDirectory,
cancellationToken);
public static async ValueTask<Option<ApiSpecificationFile>> TryParse(FileInfo? file, Func<FileInfo, ValueTask<BinaryData>> getFileContents, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken)
{
var tryParseGraphQl = () => (from specificationFile in GraphQlSpecificationFile.TryParse(file, serviceDirectory)
select specificationFile as ApiSpecificationFile).AsTask();
var tryParseWadl = () => (from specificationFile in WadlSpecificationFile.TryParse(file, serviceDirectory)
select specificationFile as ApiSpecificationFile).AsTask();
var tryParseWsdl = () => (from specificationFile in WsdlSpecificationFile.TryParse(file, serviceDirectory)
select specificationFile as ApiSpecificationFile).AsTask();
var tryParseOpenApi = async () => from specificationFile in await OpenApiSpecificationFile.TryParse(file, getFileContents, serviceDirectory, cancellationToken)
select specificationFile as ApiSpecificationFile;
return await ImmutableArray.Create(tryParseGraphQl, tryParseWadl, tryParseWsdl, tryParseOpenApi)
.Pick(async (f, cancellationToken) => await f(), cancellationToken);
}
}
public sealed record GraphQlSpecificationFile : ApiSpecificationFile
{
public override ApiSpecification Specification { get; } = new ApiSpecification.GraphQl();
public static string Name => "specification.graphql";
protected override FileInfo Value => new(Path.Combine(Parent.ToDirectoryInfo().FullName, Name));
public static GraphQlSpecificationFile From(ApiName apiName, ManagementServiceDirectory serviceDirectory) =>
new() { Parent = ApiDirectory.From(apiName, serviceDirectory) };
public static Option<GraphQlSpecificationFile> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory) =>
file is not null && file.Name == Name
? from parent in ApiDirectory.TryParse(file.Directory, serviceDirectory)
select new GraphQlSpecificationFile { Parent = parent }
: Option<GraphQlSpecificationFile>.None;
}
public sealed record WadlSpecificationFile : ApiSpecificationFile
{
public override ApiSpecification Specification { get; } = new ApiSpecification.Wadl();
public static string Name => "specification.wadl";
protected override FileInfo Value => new(Path.Combine(Parent.ToDirectoryInfo().FullName, Name));
public static WadlSpecificationFile From(ApiName apiName, ManagementServiceDirectory serviceDirectory) =>
new() { Parent = ApiDirectory.From(apiName, serviceDirectory) };
public static Option<WadlSpecificationFile> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory) =>
file is not null && file.Name == Name
? from parent in ApiDirectory.TryParse(file.Directory, serviceDirectory)
select new WadlSpecificationFile { Parent = parent }
: Option<WadlSpecificationFile>.None;
}
public sealed record WsdlSpecificationFile : ApiSpecificationFile
{
public override ApiSpecification Specification { get; } = new ApiSpecification.Wsdl();
public static string Name => "specification.wsdl";
protected override FileInfo Value => new(Path.Combine(Parent.ToDirectoryInfo().FullName, Name));
public static WsdlSpecificationFile From(ApiName apiName, ManagementServiceDirectory serviceDirectory) =>
new() { Parent = ApiDirectory.From(apiName, serviceDirectory) };
public static Option<WsdlSpecificationFile> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory) =>
file is not null && file.Name == Name
? from parent in ApiDirectory.TryParse(file.Directory, serviceDirectory)
select new WsdlSpecificationFile { Parent = parent }
: Option<WsdlSpecificationFile>.None;
}
public abstract record OpenApiSpecificationFile : ApiSpecificationFile
{
public abstract OpenApiFormat Format { get; }
public required OpenApiVersion Version { get; init; }
public static OpenApiSpecificationFile From(ApiSpecification.OpenApi openApi, ApiName apiName, ManagementServiceDirectory serviceDirectory) =>
openApi.Format switch
{
OpenApiFormat.Json => JsonOpenApiSpecificationFile.From(openApi.Version, apiName, serviceDirectory),
OpenApiFormat.Yaml => YamlOpenApiSpecificationFile.From(openApi.Version, apiName, serviceDirectory),
_ => throw new NotImplementedException()
};
public static new async ValueTask<Option<OpenApiSpecificationFile>> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) =>
await TryParse(file,
getFileContents: async file => await file.ReadAsBinaryData(cancellationToken),
serviceDirectory,
cancellationToken);
public static new async ValueTask<Option<OpenApiSpecificationFile>> TryParse(FileInfo? file, Func<FileInfo, ValueTask<BinaryData>> getFileContents, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken)
{
var tryParseYaml = async () => from yaml in await YamlOpenApiSpecificationFile.TryParse(file, getFileContents, serviceDirectory, cancellationToken)
select yaml as OpenApiSpecificationFile;
var tryParseJson = async () => from json in await JsonOpenApiSpecificationFile.TryParse(file, getFileContents, serviceDirectory, cancellationToken)
select json as OpenApiSpecificationFile;
return await ImmutableArray.Create(tryParseYaml, tryParseJson)
.Pick(async (f, cancellationToken) => await f(), cancellationToken);
}
}
public sealed record YamlOpenApiSpecificationFile : OpenApiSpecificationFile
{
public override OpenApiFormat Format { get; } = new OpenApiFormat.Yaml();
public override ApiSpecification Specification => new ApiSpecification.OpenApi
{
Format = Format,
Version = Version
};
public static string Name { get; } = "specification.yaml";
protected override FileInfo Value => new(Path.Combine(Parent.ToDirectoryInfo().FullName, Name));
public static YamlOpenApiSpecificationFile From(OpenApiVersion version, ApiName apiName, ManagementServiceDirectory serviceDirectory) =>
new()
{
Parent = ApiDirectory.From(apiName, serviceDirectory),
Version = version
};
public static new async ValueTask<Option<YamlOpenApiSpecificationFile>> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) =>
await TryParse(file,
getFileContents: async file => await file.ReadAsBinaryData(cancellationToken),
serviceDirectory,
cancellationToken);
public static new async ValueTask<Option<YamlOpenApiSpecificationFile>> TryParse(FileInfo? file, Func<FileInfo, ValueTask<BinaryData>> getFileContents, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) =>
file is not null && file.Name == Name
? await ApiDirectory.TryParse(file.Directory, serviceDirectory)
.BindTask(async parent => from version in await OpenApiVersion.TryParse(await getFileContents(file), cancellationToken)
select new YamlOpenApiSpecificationFile
{
Parent = parent,
Version = version
})
: Option<YamlOpenApiSpecificationFile>.None;
}
public sealed record JsonOpenApiSpecificationFile : OpenApiSpecificationFile
{
public override OpenApiFormat Format { get; } = new OpenApiFormat.Json();
public override ApiSpecification Specification => new ApiSpecification.OpenApi
{
Format = Format,
Version = Version
};
public static string Name { get; } = "specification.json";
protected override FileInfo Value => new(Path.Combine(Parent.ToDirectoryInfo().FullName, Name));
public static JsonOpenApiSpecificationFile From(OpenApiVersion version, ApiName apiName, ManagementServiceDirectory serviceDirectory) =>
new()
{
Parent = ApiDirectory.From(apiName, serviceDirectory),
Version = version
};
public static new async ValueTask<Option<JsonOpenApiSpecificationFile>> TryParse(FileInfo? file, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) =>
await TryParse(file,
getFileContents: async file => await file.ReadAsBinaryData(cancellationToken),
serviceDirectory,
cancellationToken);
public static new async ValueTask<Option<JsonOpenApiSpecificationFile>> TryParse(FileInfo? file, Func<FileInfo, ValueTask<BinaryData>> getFileContents, ManagementServiceDirectory serviceDirectory, CancellationToken cancellationToken) =>
file is not null && file.Name == Name
? await ApiDirectory.TryParse(file.Directory, serviceDirectory)
.BindTask(async parent => from version in await OpenApiVersion.TryParse(await getFileContents(file), cancellationToken)
select new JsonOpenApiSpecificationFile
{
Parent = parent,
Version = version
})
: Option<JsonOpenApiSpecificationFile>.None;
}