diff --git a/ArchiSteamFarm/IPC/Integration/BotConfigSchemaFilter.cs b/ArchiSteamFarm/IPC/Integration/BotConfigSchemaFilter.cs deleted file mode 100644 index fe85aa4bc..000000000 --- a/ArchiSteamFarm/IPC/Integration/BotConfigSchemaFilter.cs +++ /dev/null @@ -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; - } - } - } -} diff --git a/ArchiSteamFarm/IPC/Integration/GlobalConfigSchemaFilter.cs b/ArchiSteamFarm/IPC/Integration/CustomAttributesSchemaFilter.cs similarity index 66% rename from ArchiSteamFarm/IPC/Integration/GlobalConfigSchemaFilter.cs rename to ArchiSteamFarm/IPC/Integration/CustomAttributesSchemaFilter.cs index 4a78858d9..ba742fef0 100644 --- a/ArchiSteamFarm/IPC/Integration/GlobalConfigSchemaFilter.cs +++ b/ArchiSteamFarm/IPC/Integration/CustomAttributesSchemaFilter.cs @@ -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()?.Apply(schema); + context.MemberInfo.GetCustomAttribute()?.Apply(schema); + context.MemberInfo.GetCustomAttribute()?.Apply(schema); } } } diff --git a/ArchiSteamFarm/IPC/Integration/SwaggerItemsMinMaxAttribute.cs b/ArchiSteamFarm/IPC/Integration/SwaggerItemsMinMaxAttribute.cs new file mode 100644 index 000000000..c520cfb41 --- /dev/null +++ b/ArchiSteamFarm/IPC/Integration/SwaggerItemsMinMaxAttribute.cs @@ -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; + } + } + } +} diff --git a/ArchiSteamFarm/IPC/Integration/SwaggerSteamIdentifierAttribute.cs b/ArchiSteamFarm/IPC/Integration/SwaggerSteamIdentifierAttribute.cs new file mode 100644 index 000000000..7e390e92a --- /dev/null +++ b/ArchiSteamFarm/IPC/Integration/SwaggerSteamIdentifierAttribute.cs @@ -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); + } + } +} diff --git a/ArchiSteamFarm/IPC/Integration/SwaggerValidValuesAttribute.cs b/ArchiSteamFarm/IPC/Integration/SwaggerValidValuesAttribute.cs new file mode 100644 index 000000000..363ca2b3c --- /dev/null +++ b/ArchiSteamFarm/IPC/Integration/SwaggerValidValuesAttribute.cs @@ -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); + } + } + } +} diff --git a/ArchiSteamFarm/IPC/Startup.cs b/ArchiSteamFarm/IPC/Startup.cs index 9a2ade289..190b3e84b 100644 --- a/ArchiSteamFarm/IPC/Startup.cs +++ b/ArchiSteamFarm/IPC/Startup.cs @@ -217,9 +217,8 @@ namespace ArchiSteamFarm.IPC { options.CustomSchemaIds(type => type.GetUnifiedName()); options.EnableAnnotations(true, true); - options.SchemaFilter(); + options.SchemaFilter(); options.SchemaFilter(); - options.SchemaFilter(); options.SwaggerDoc( SharedInfo.ASF, new OpenApiInfo { diff --git a/ArchiSteamFarm/Steam/Storage/BotConfig.cs b/ArchiSteamFarm/Steam/Storage/BotConfig.cs index 810be6e9f..bfc83b5fe 100644 --- a/ArchiSteamFarm/Steam/Storage/BotConfig.cs +++ b/ArchiSteamFarm/Steam/Storage/BotConfig.cs @@ -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 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 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? 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(); + + 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))) { diff --git a/ArchiSteamFarm/Storage/GlobalConfig.cs b/ArchiSteamFarm/Storage/GlobalConfig.cs index 8bbf099d9..c888d37be 100644 --- a/ArchiSteamFarm/Storage/GlobalConfig.cs +++ b/ArchiSteamFarm/Storage/GlobalConfig.cs @@ -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 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)]