Make custom swagger limitations more generic to use

This commit is contained in:
Archi 2021-06-30 22:23:01 +02:00
parent ccc7a3ed32
commit 0060e76829
No known key found for this signature in database
GPG key ID: 6B138B4C64555AEA
8 changed files with 205 additions and 115 deletions

View file

@ -1,87 +0,0 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Linq;
using ArchiSteamFarm.Steam.Storage;
using JetBrains.Annotations;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using SteamKit2;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace ArchiSteamFarm.IPC.Integration {
[UsedImplicitly]
internal sealed class BotConfigSchemaFilter : ISchemaFilter {
public void Apply(OpenApiSchema schema, SchemaFilterContext context) {
if (schema == null) {
throw new ArgumentNullException(nameof(schema));
}
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
if (context.MemberInfo?.DeclaringType != typeof(BotConfig)) {
return;
}
OpenApiArray validValues;
switch (context.MemberInfo.Name) {
case nameof(BotConfig.CompleteTypesToSend):
validValues = new OpenApiArray();
validValues.AddRange(BotConfig.AllowedCompleteTypesToSend.Select(type => new OpenApiInteger((int) type)));
// Note, we'd love to add this to schema.Items, but since items are ref to the enum, it's not possible to add it that way
schema.AddExtension("x-valid-values", validValues);
break;
case nameof(BotConfig.GamesPlayedWhileIdle):
schema.Items.Minimum = 1;
schema.Items.Maximum = uint.MaxValue;
break;
case nameof(BotConfig.SteamMasterClanID):
schema.Minimum = new SteamID(1, EUniverse.Public, EAccountType.Clan);
schema.Maximum = new SteamID(uint.MaxValue, EUniverse.Public, EAccountType.Clan);
validValues = new OpenApiArray();
validValues.Add(new OpenApiInteger(0));
schema.AddExtension("x-valid-values", validValues);
break;
case nameof(BotConfig.SteamParentalCode):
validValues = new OpenApiArray();
validValues.Add(new OpenApiString("0"));
schema.AddExtension("x-valid-values", validValues);
break;
}
}
}
}

View file

@ -20,17 +20,14 @@
// limitations under the License.
using System;
using ArchiSteamFarm.Storage;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using SteamKit2;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace ArchiSteamFarm.IPC.Integration {
[UsedImplicitly]
internal sealed class GlobalConfigSchemaFilter : ISchemaFilter {
internal sealed class CustomAttributesSchemaFilter : ISchemaFilter {
public void Apply(OpenApiSchema schema, SchemaFilterContext context) {
if (schema == null) {
throw new ArgumentNullException(nameof(schema));
@ -40,28 +37,13 @@ namespace ArchiSteamFarm.IPC.Integration {
throw new ArgumentNullException(nameof(context));
}
if (context.MemberInfo?.DeclaringType != typeof(GlobalConfig)) {
if (context.MemberInfo == null) {
return;
}
switch (context.MemberInfo.Name) {
case nameof(GlobalConfig.Blacklist):
schema.Items.Minimum = 1;
schema.Items.Maximum = uint.MaxValue;
break;
case nameof(GlobalConfig.SteamOwnerID):
schema.Minimum = new SteamID(1, EUniverse.Public, EAccountType.Individual);
schema.Maximum = new SteamID(uint.MaxValue, EUniverse.Public, EAccountType.Individual);
OpenApiArray validValues = new();
validValues.Add(new OpenApiInteger(0));
schema.AddExtension("x-valid-values", validValues);
break;
}
context.MemberInfo.GetCustomAttribute<SwaggerItemsMinMaxAttribute>()?.Apply(schema);
context.MemberInfo.GetCustomAttribute<SwaggerSteamIdentifierAttribute>()?.Apply(schema);
context.MemberInfo.GetCustomAttribute<SwaggerValidValuesAttribute>()?.Apply(schema);
}
}
}

View file

@ -0,0 +1,62 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
namespace ArchiSteamFarm.IPC.Integration {
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Struct)]
[PublicAPI]
public sealed class SwaggerItemsMinMaxAttribute : ValidationAttribute {
public uint MaximumUint {
get => BackingMaximum.HasValue ? decimal.ToUInt32(BackingMaximum.Value) : default(uint);
set => BackingMaximum = value;
}
public uint MinimumUint {
get => BackingMinimum.HasValue ? decimal.ToUInt32(BackingMinimum.Value) : default(uint);
set => BackingMinimum = value;
}
private decimal? BackingMaximum;
private decimal? BackingMinimum;
public void Apply(OpenApiSchema schema) {
if (schema == null) {
throw new ArgumentNullException(nameof(schema));
}
if (schema.Items == null) {
throw new InvalidOperationException(nameof(schema.Items));
}
if (BackingMinimum.HasValue) {
schema.Items.Minimum = BackingMinimum.Value;
}
if (BackingMaximum.HasValue) {
schema.Items.Maximum = BackingMaximum.Value;
}
}
}
}

View file

@ -0,0 +1,46 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using SteamKit2;
namespace ArchiSteamFarm.IPC.Integration {
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Struct)]
[PublicAPI]
public sealed class SwaggerSteamIdentifierAttribute : ValidationAttribute {
public EAccountType AccountType { get; set; } = EAccountType.Individual;
public uint MaximumAccountID { get; set; } = uint.MaxValue;
public uint MinimumAccountID { get; set; } = 1;
public EUniverse Universe { get; set; } = EUniverse.Public;
public void Apply(OpenApiSchema schema) {
if (schema == null) {
throw new ArgumentNullException(nameof(schema));
}
schema.Minimum = new SteamID(MinimumAccountID, Universe, AccountType);
schema.Maximum = new SteamID(MaximumAccountID, Universe, AccountType);
}
}
}

View file

@ -0,0 +1,59 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
namespace ArchiSteamFarm.IPC.Integration {
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Struct)]
[PublicAPI]
public sealed class SwaggerValidValuesAttribute : ValidationAttribute {
public int[]? ValidIntValues { get; set; }
public string[]? ValidStringValues { get; set; }
public void Apply(OpenApiSchema schema) {
if (schema == null) {
throw new ArgumentNullException(nameof(schema));
}
OpenApiArray validValues = new();
if (ValidIntValues != null) {
validValues.AddRange(ValidIntValues.Select(type => new OpenApiInteger(type)));
}
if (ValidStringValues != null) {
validValues.AddRange(ValidStringValues.Select(type => new OpenApiString(type)));
}
if (schema.Items is { Reference: null }) {
schema.Items.AddExtension("x-valid-values", validValues);
} else {
schema.AddExtension("x-valid-values", validValues);
}
}
}
}

View file

@ -217,9 +217,8 @@ namespace ArchiSteamFarm.IPC {
options.CustomSchemaIds(type => type.GetUnifiedName());
options.EnableAnnotations(true, true);
options.SchemaFilter<BotConfigSchemaFilter>();
options.SchemaFilter<CustomAttributesSchemaFilter>();
options.SchemaFilter<EnumSchemaFilter>();
options.SchemaFilter<GlobalConfigSchemaFilter>();
options.SwaggerDoc(
SharedInfo.ASF, new OpenApiInfo {

View file

@ -32,9 +32,11 @@ using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.IPC.Integration;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.Steam.Data;
using ArchiSteamFarm.Steam.Integration;
@ -156,6 +158,7 @@ namespace ArchiSteamFarm.Steam.Storage {
public EBotBehaviour BotBehaviour { get; private set; } = DefaultBotBehaviour;
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerValidValues(ValidIntValues = new[] { (int) Asset.EType.TradingCard, (int) Asset.EType.FoilTradingCard })]
public ImmutableHashSet<Asset.EType> CompleteTypesToSend { get; private set; } = DefaultCompleteTypesToSend;
[JsonProperty]
@ -175,6 +178,7 @@ namespace ArchiSteamFarm.Steam.Storage {
[JsonProperty(Required = Required.DisallowNull)]
[MaxLength(ArchiHandler.MaxGamesPlayedConcurrently)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> GamesPlayedWhileIdle { get; private set; } = DefaultGamesPlayedWhileIdle;
[JsonProperty(Required = Required.DisallowNull)]
@ -223,11 +227,14 @@ namespace ArchiSteamFarm.Steam.Storage {
}
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerSteamIdentifier(AccountType = EAccountType.Clan)]
[SwaggerValidValues(ValidIntValues = new[] { 0 })]
public ulong SteamMasterClanID { get; private set; } = DefaultSteamMasterClanID;
[JsonProperty]
[MaxLength(SteamParentalCodeLength)]
[MinLength(SteamParentalCodeLength)]
[SwaggerValidValues(ValidStringValues = new[] { "0" })]
public string? SteamParentalCode {
get => BackingSteamParentalCode;
@ -387,8 +394,26 @@ namespace ArchiSteamFarm.Steam.Storage {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(LootableTypes), lootableType));
}
foreach (Asset.EType completableType in CompleteTypesToSend.Where(completableType => !Enum.IsDefined(typeof(Asset.EType), completableType) || !AllowedCompleteTypesToSend.Contains(completableType))) {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(CompleteTypesToSend), completableType));
HashSet<Asset.EType>? completeTypesToSendValidTypes = null;
foreach (Asset.EType completableType in CompleteTypesToSend) {
if (!Enum.IsDefined(typeof(Asset.EType), completableType)) {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(CompleteTypesToSend), completableType));
}
if (completeTypesToSendValidTypes == null) {
SwaggerValidValuesAttribute? completeTypesToSendValidValues = typeof(BotConfig).GetProperty(nameof(CompleteTypesToSend))?.GetCustomAttribute<SwaggerValidValuesAttribute>();
if (completeTypesToSendValidValues?.ValidIntValues == null) {
throw new InvalidOperationException(nameof(completeTypesToSendValidValues));
}
completeTypesToSendValidTypes = completeTypesToSendValidValues.ValidIntValues.Select(value => (Asset.EType) value).ToHashSet();
}
if (!completeTypesToSendValidTypes.Contains(completableType)) {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(CompleteTypesToSend), completableType));
}
}
foreach (Asset.EType matchableType in MatchableTypes.Where(matchableType => !Enum.IsDefined(typeof(Asset.EType), matchableType))) {

View file

@ -30,6 +30,7 @@ using System.Net;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.IPC.Integration;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.Steam.Integration;
using JetBrains.Annotations;
@ -178,6 +179,7 @@ namespace ArchiSteamFarm.Storage {
public bool AutoRestart { get; private set; } = DefaultAutoRestart;
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> Blacklist { get; private set; } = DefaultBlacklist;
[JsonProperty]
@ -248,6 +250,8 @@ namespace ArchiSteamFarm.Storage {
public string? SteamMessagePrefix { get; private set; } = DefaultSteamMessagePrefix;
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerSteamIdentifier]
[SwaggerValidValues(ValidIntValues = new[] { 0 })]
public ulong SteamOwnerID { get; private set; } = DefaultSteamOwnerID;
[JsonProperty(Required = Required.DisallowNull)]