Skip to content

Use OpenAPI schema for config editor fields, fixes #871 - #1767

Open
UmmmAGoodName wants to merge 10 commits into
JustArchiNET:mainfrom
UmmmAGoodName:openapi-config-schema
Open

UmmmAGoodName wants to merge 10 commits into
JustArchiNET:mainfrom
UmmmAGoodName:openapi-config-schema

Conversation

@UmmmAGoodName

Copy link
Copy Markdown
Contributor

Description

Fixes #871, supersedes #1022.

fetchConfigSchema.js no longer calls GET /Api/Type and no longer parses .NET generic type strings with regexes. Field schemas for the config editor are now derived from ASF's OpenAPI document (swagger/ASF/swagger.json), which utils/swagger/parse.js already fetches, caches and $ref-dereferences for the currency codes, so the document is still requested only once per page load. unwindType() keeps its name, signature and output shape, so Editor.vue, the Fields/ components and all views that call it are untouched.

Mapping from the OpenAPI property schema to the editor field type:

OpenAPI property schema field type
type: boolean boolean
type: string / ["null", "string"] string (nullable: true when null is listed)
format: uuid (LicenseID) string, as before (Editor.vue would accept guid, but isEqual() has no guid case, so switching would change default detection; left for a follow-up)
format: uint8 / uint16 / uint32 / uint64 byte / uint16 / uint32 / uint64 (uint64 still gets the s_ paramName)
type: array hashSet; list for FarmingOrders and GamesPlayedWhileIdle, which are ImmutableList in ASF. OpenAPI cannot tell sets and lists apart, so these are hardcoded (verified against /Api/Type on 6.3.9.6). values = resolved items
type: object + additionalProperties dictionary; key is string, except SteamUserPermissions (Dictionary<ulong, EAccess>) → uint64. value = resolved additionalProperties
$ref to a schema with x-definition flag when format: flags, else enum; values = the x-definition map
anything else unknown (InputUnknown fallback)

Properties named s_* are skipped: the spec lists SteamOwnerID and s_SteamOwnerID (and the clan ID pair) separately, while the UI derives the s_ name from the uint64 property as before.

Defaults: the OpenAPI schema carries no default values, so GET /Api/Structure/{type} is still used (one cached call per type) for defaultValue. ASF can drop /Api/Type as far as ASF-ui is concerned, but not /Api/Structure yet.

Passthrough keys (not consumed yet): minimum, maximum, maxItems, x-valid-values → validValues and x-security-critical → securityCritical are copied onto each field schema for #1445 / #1650. No UI logic uses them in this PR.

Screenshots

No visual changes by design. ASF config, bot config, Create Bot and the Mass Editor render the same inputs as before: flag multi-selects and enum selects show names, FarmingOrders is an ordered list, SteamUserPermissions uses the select-based dictionary editor and GamesPlayedWhileIdle uses InputGames. The only observable difference is in the network tab: opening a config page no longer issues the ~20 /Api/Type/... requests, only swagger.json and one /Api/Structure/... call.

Additional information

Verified against a live ASF 6.3.9.6 instance. The old and new implementations were run side by side and the resulting body objects deep-diffed for both BotConfig (34 fields) and GlobalConfig (35 fields). Every type, values, key/value, paramName and defaultValue is identical. The only differences are in the nullable key, which nothing in the UI reads:

  • the old code could only see Nullable<T> value types via reflection, so string? properties never got nullable; the new code marks every ["null", "string"] property as nullable;
  • LicenseID (Guid?) is described by the spec as { format: uuid } with no type, so it loses nullable: true.

Two small things to be aware of:

  • Fields are now emitted in the order of the OpenAPI document instead of .NET reflection order. Views that sort (categories on/off) are unaffected; Create Bot and uncategorized fields now appear in spec order.
  • The mapping relies on the shape of the spec that ASF 6.3.x generates (type arrays with null, format: uint8/…/uint64, format: flags, x-definition). A spec with a different shape would degrade affected fields to the InputUnknown fallback rather than fail.

A follow-up could let ASF mark ordered collections and ulong-keyed dictionaries in the spec (e.g. an x- extension), which would remove the two hardcoded property sets in this file.

Checklist

  • My pull request is not a duplicate - It is, but the old PR has gone stale.
  • I added a descriptive title to this pull request
  • I added a concise description or a self-explanatory screenshot to this pull request
  • My code follows the code style of this project
  • I have performed a self-review of my own code

JustArchi added a commit to JustArchiNET/ArchiSteamFarm that referenced this pull request Oct 11, 2026
@JustArchi

JustArchi commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Thanks for pointing out ASF flaws, I agree with all stated here.

Defaults: the OpenAPI schema carries no default values, so GET /Api/Structure/{type} is still used (one cached call per type) for defaultValue. ASF can drop /Api/Type as far as ASF-ui is concerned, but not /Api/Structure yet.

I've added proper support for default in ASF, below commit for reference:

JustArchiNET/ArchiSteamFarm@a39fb49

Now properties carry proper default value:

              "OnlineStatus": {
                "type": "integer",
                "default": 1,
                "x-definition": {
                  "Offline": 0,
                  "Online": 1,
                  "Busy": 2,
                  "Away": 3,
                  "Snooze": 4,
                  "LookingToTrade": 5,
                  "LookingToPlay": 6,
                  "Invisible": 7
                }

It works even for complex types like BotConfig input:

            "default": {
              "WebProxy": null,
              "AcceptGifts": false,
              "BotBehaviour": 0,
              "CompleteTypesToSend": [ ],
              "CustomGamePlayedWhileFarming": null,
              "CustomGamePlayedWhileIdle": null,
              "Enabled": false,
              "FarmingOrders": [ ],
              "FarmingPreferences": 0,
              "GamesPlayedWhileIdle": [ ],
              "GamingDeviceType": 1,
              "HoursUntilCardDrops": 3,
              "LootableTypes": [
                1,
                3,
                5
              ],
              "MachineName": null,
              "MatchableTypes": [
                5
              ],
              "OnlineFlags": 0,
              "OnlineStatus": 1,
              "PasswordFormat": 0,
              "RedeemingPreferences": 0,
              "RemoteCommunication": 3,
              "SendTradePeriod": 0,
              "SteamMasterClanID": 0,
              "SteamTradeToken": null,
              "SteamUserPermissions": { },
              "TradeCheckPeriod": 60,
              "TradingPreferences": 0,
              "TransferableTypes": [
                1,
                3,
                5
              ],
              "UseLoginKeys": true,
              "UserInterfaceMode": 0,
              "WebProxyUsername": null,
              "s_SteamMasterClanID": "0"
            }

I believe no Structure call should ever be needed, correct me if I'm wrong.

A follow-up could let ASF mark ordered collections and ulong-keyed dictionaries in the spec (e.g. an x- extension), which would remove the two hardcoded property sets in this file.

I'll see tomorrow what can be done about that.

JustArchi added a commit to JustArchiNET/ArchiSteamFarm that referenced this pull request Oct 12, 2026
@JustArchi

JustArchi commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

After implementing JustArchiNET/ArchiSteamFarm@3d5b45b - ASF-ui can now distinguish sets from lists by looking up whether uniqueItems: true exists in the declaration.

              "FarmingOrders": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/EFarmingOrder"
                },
                "default": [ ]
              "CompleteTypesToSend": {
                "uniqueItems": true,
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/EAssetType"
                },
                "default": [ ]
              },

This can also be used for determining whether order matters, e.g. for GamesPlayedWhileIdle, which is a specific property that should carry uniqueItems but it does not, to allow order to be preserved.

JustArchi added a commit to JustArchiNET/ArchiSteamFarm that referenced this pull request Oct 12, 2026
@JustArchi

Copy link
Copy Markdown
Member

Finally, after JustArchiNET/ArchiSteamFarm@0eb23c8, it's now possible to determine exact key type in all dictionaries:

              "SteamUserPermissions": {
                "propertyNames": {
                  "maxLength": 20,
                  "pattern": "^[0-9]+$",
                  "type": "string"
                },
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/components/schemas/EAccess"
                },
                "x-key-type": "System.UInt64"
              },

I went with hybrid approach here, that is:

  • We support RFC-compliant propertyNames and describe in best possible way key type validation that needs to pass
  • And due to the fact that above is not precise enough, x-key-type also exists that describes very specific CLR type used.

That should cover all needs in this PR:

  • It's now possible to determine what default value is set for given property
  • It's now possible to determine whether property accepts only uniqueItems: true
  • It's now possible to determine what x-key-type is used for type: object properties (with extra RFC-compliant bits for tools that do not understand our extensions)

Let me know if you stumble upon any other limitations @UmmmAGoodName

@UmmmAGoodName

Copy link
Copy Markdown
Contributor Author

Thanks, that covers everything this PR needed. Updated in the latest commit:

  • BotConfig / GlobalConfig are now read from the POST /Api/Bot/{botNames} and POST /Api/ASF request bodies, since they're no longer under components.schemas after the inlining change.
  • default replaces /Api/Structure entirely, uniqueItems replaces the hardcoded list of ordered properties, and x-key-type replaces the hardcoded dictionary key type.
  • Verified against 6.3.11.0: all 69 fields resolve identically to the old /Api/Type + /Api/Structure implementation, and no /Api/Type or /Api/Structure calls remain.

Two small things I noticed while testing against 6.3.11.0:

  1. SteamUserPermissions is the only config property without its own default (every other property has one). The object-level default on BotConfig does include it as {}, so I fall back to that, but a property-level default would be more consistent.
  2. ECurrencyCode is no longer exposed under components.schemas (it's inlined under Bot.properties.WalletCurrency now), so getDefinitions('ECurrencyCode') in store/modules/asf.js throws on current main against 6.3.11.0. Not touched in this PR since it's unrelated to the config editor, but it will need the same treatment.

@JustArchi

JustArchi commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

ECurrencyCode is no longer exposed under components.schemas (it's inlined under Bot.properties.WalletCurrency now), so getDefinitions('ECurrencyCode') in store/modules/asf.js throws on current main against 6.3.11.0. Not touched in this PR since it's unrelated to the config editor, but it will need the same treatment.

Yeah, ASF-ui can't assume exact placement of some schema component - instead, it should traverse the tree in the expected way, that is, look up schema definition for given property in request/response that interests you, and grab definition from there - either directly (if it's inlined), or by navigating to appropriate $ref.

Inlining was required since the same schemas can have different default values depending on usage. The current code is smart and tries to avoid duplicate inlines - this is why you see combination of $ref schemas, as well as inlined ones.

Definitely no assumption can be done whether given schema is inlined or not - it can and will change across different ASF versions. Caller must support both - inlined, as well as referenced schema definition for given property. It should not be a problem - just look up if you have $ref to work with, and if yes, grab final object from there.

In any case, the only proper lookup can be done by analyzing the request/response property that you want to understand - not by taking arbitrary unlinked component that just happened to have correctly matching name.

SteamUserPermissions is the only config property without its own default (every other property has one). The object-level default on BotConfig does include it as {}, so I fall back to that, but a property-level default would be more consistent.

I'll take a look, chance is we can fix that.

JustArchi added a commit to JustArchiNET/ArchiSteamFarm that referenced this pull request Oct 13, 2026
@JustArchi

JustArchi commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

SteamUserPermissions is the only config property without its own default (every other property has one). The object-level default on BotConfig does include it as {}, so I fall back to that, but a property-level default would be more consistent.

@UmmmAGoodName Fixed in V6.3.11.1, thanks for info. Let me know if you discover any other inconsistencies.

@UmmmAGoodName

Copy link
Copy Markdown
Contributor Author

Thanks for the quick fix. Verified against 6.3.11.1 and pushed two more commits:

  • SteamUserPermissions now carries default: {} as expected, and every config property has its own default, so I dropped the object-level fallback. The loader reads defaults from the property schema only, nothing else.
  • Following your note on schema lookup, the currency codes are now resolved through the GET /Api/Bot/{botNames} response schema down to the bot's WalletCurrency enum instead of the ECurrencyCode component, which fixes the wallet currency display on 6.3.11.x. With that, the two by-component-name helpers in utils/swagger/parse.js had no callers left and are removed, so there is no lookup by component name anywhere in ASF-ui anymore. It's in a separate commit since it's outside Missing text in bot config editor #871, happy to move it to its own PR if you prefer.

With this the PR requires ASF 6.3.11.1 or newer. No /Api/Type or /Api/Structure calls remain, and all 69 config fields resolve identically to the old implementation.

@JustArchi

Copy link
Copy Markdown
Member

@MrBurrBurr care to check the status of this PR and whether it's possible to merge? It seems to do far more good than bad, and also fixes some outstanding ASF-ui issues that we had before.

@Aareksio

Aareksio commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

@UmmmAGoodName New default value for SteamPassword makes Mass Editor delete passwords from config if the field is touched.

curl -X POST localhost:1242/Api/Bot/test -H 'Content-Type: application/json' \
  -d '{"BotConfig":{"Enabled":true,"SteamPassword":null}}'

This affects all sensitive fields, including ones which need clear distinction between empty and null (SteamParentalCode)


After discussing with Archi:

  • current default for these fields is undefined, there is currently no way to send null and clear the field
  • PR should preserve this behavior and compute default: null + x-security-critical -> undefined
  • as a separate feature, we can update the UI to add "set empty" (or "keep current") action

@JustArchi

JustArchi commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

As per what @Aareksio said:

  • ASF-ui does not know current value of x-security-critical properties, it also does not know whether the value is set at all, which is fine as this information is not critical for operation
  • Default behaviour for x-security-critical in UI should be to preserve existing value, regardless whether it's set or not
  • User should be able to input new value
  • Wishlist for later: user should be able to remove existing value

For keeping existing value:

{}

For setting new value:

{ "SteamPassword": "new" }

For removing value, same concept as above (assumes default for this field is null):

{ "SteamPassword": null }

All of that can be done with existing ASF API functionality and swagger definition.

Therefore, fix seems to be NOT to render default value for x-security-critical fields unless user decided to set it to a new value or remove completely.

@UmmmAGoodName

Copy link
Copy Markdown
Contributor Author

Thanks @Aareksio, good catch. Fixed in 767c2bf and verified against 6.3.11.3: properties carrying x-security-critical now get no defaultValue at all, exactly as the /Api/Structure based code behaved. In the Mass Editor, selecting SteamPassword or WebProxyPassword without entering a value now results in an empty change set, the editor reports "already the same value" and nothing is posted.

There is one gap this leaves, and I'd rather fix it in ASF than in ASF-ui:

SteamLogin and SteamParentalCode are omitted from API responses the same way SteamPassword is, and the old UI therefore had no default for them either. In the current OpenAPI document they are not marked x-security-critical, so they carry default: null, and selecting either of them in the Mass Editor still seeds the change set with null and would clear the stored value on save. That is exactly the SteamParentalCode case you mentioned.

I could hardcode those two names in ASF-ui as a workaround, but that's the kind of assumption we just removed from the schema loader (lists, dictionary keys, component names), and it would silently break the next time a sensitive property is added or renamed. The clean fix on the ASF side would be:

  • Mark SteamLogin and SteamParentalCode with x-security-critical (BotConfig), so the spec matches what ASF actually withholds from API responses.
  • More generally: every property whose value ASF never returns via the API should carry x-security-critical, since "the UI cannot know whether a value is set" is the property that drives the no-default rule. If there are others (I only compared BotConfig and GlobalConfig), the same applies.

With that in place the rule in ASF-ui stays a single line, x-security-critical → no default, and the remaining difference to the old implementation disappears. For reference, after the fix the only properties whose default differs from the old /Api/Structure output are exactly those two; GlobalConfig has none.

JustArchi added a commit to JustArchiNET/ArchiSteamFarm that referenced this pull request Oct 20, 2026
@JustArchi

JustArchi commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Missing x-security-critical on steam login and parental code was totally oversight on ASF's part - this is now fixed with V6.3.11.4.

It's proper assumption that every single property behaving this way must carry x-security-critical adnotation - ASF-ui and other consumers can depend on that detail to be true. I've manually verified that now all properties carry proper adnotation, only those two were missing it.

@JustArchi
JustArchi requested a review from Aareksio October 20, 2026 19:38
@Aareksio

Copy link
Copy Markdown
Member

LGTM, @JustArchi if possible, I would love for this to land in pre-release ASF and wait a little (2 week-ish) before being promoted to stable. Just in case of hidden user-facing regression synthetic tests omitted.

@JustArchi

JustArchi commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

In this case we can wait a few days to a week more, and I'll release a new monthly pre-release cycle with this bundled. You get like full month till stable then.

I can merge the PR myself then when ready, thanks!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Missing text in bot config editor

3 participants