Repository navigation
Add the field type registry and the core field types - #2728
Merged
Merged
Conversation
# Conflicts: # src/FluentCMS.Sdk/PublicAPI.Unshipped.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2684
Every field type now describes itself in one place:
IFieldTypein the SDK (CLR types, storage kind, filter operators, sortable and indexable flags, accepted settings, coercion of raw values, OpenAPI fragment) with anIFieldTypeRegistry. Core registers the 16 core descriptors inFieldTypeRegistry, which also takes extra descriptors from plugins.SchemaBuilderandSchemaValidatoruse the registry in place of the fixedKnownFieldTypeslist, and the validator now checks each field's settings against its descriptor, so a typo such asmaxlengthorrequirdinschema.jsonfails startup withFCMS1211instead of being dropped silently (the review note on #2724).Dependencies: none.
Acceptance
blocks,boolean,component,date,dateTime,decimal,email,integer,json,longText,relation,richText,select,slug,text,url. Coercion and settings validation are tested; the declarative rules (Required,Length,Range,RegularExpression, option membership) belong to Declarative validation, custom validators and serialisable conditions #2685.mediadescriptor registered throughnew FieldTypeRegistry([new MediaFieldType()])lets a[Media]class build, and the field is in theSchemaModelwith storage kindReference. Without it the build fails withFCMS1203.SlugFieldType.Slugifyand the slug pattern are done and tested. Uniqueness is not enforced here: the partial unique index in EF Core model builder: documents, content tables, locale rows, references and versions #2688 covers published entries and the draft-side check in IContentService and IContentService<T> with optimistic concurrency #2693 covers drafts.All,Find, and per typeStorageKind,FilterOperators,Sortable,Indexable,SettingsandOpenApiSchema, with the shape of each fragment tested.dotnet build -c Release -warnaserroranddotnet test -c Releasepass.Beyond the spec
IFieldType.AcceptsAnySettingis a default interface member (false). A plugin type that keeps free-form options returnstrueand then skips the unknown-key and value-kind checks; its required settings are still checked. Core types never accept unknown keys. This changedSchemaJsonTests.UnknownFieldPropertiesBecomeSettingsOfTheFieldType, which used atextfield with arbitrary settings; it now uses a test type that accepts any setting.FCMS1211. The error names the type, the field and the key:text field article.title has an unknown setting 'maxlength'; it accepts none. A missing required key onselect,componentandblocksis left to the existing type-specific checks so it is reported once, with their messages.TryCoerceout parameter is namedmessagerather thanerror, because CA1716 forbidserroron an interface member.Sortable(SchemaField)andIndexable(SchemaField)are methods, likeStorageKind(SchemaField). Core types answerfalsetoSortablefor a field stored as JSON (multi select, repeatable component,richText,json,blocks) andfalsetoIndexablefor a multi select and a repeatable component.jsonandblocksare indexable (GIN on PostgreSQL, spec 4.3);blocksis not sortable.FilterOperatorsstays a property of the type; forselectit includesContains, meant for a multi select.FieldSettingDescriptoris asealed recordwithrequiredKeyandKindand an optionalRequired, allinit, so a member can be added later without a binary break.JsonElementor aJsonNode, and every type reads all three (RawValue.Unwrapturns a node into an element first). TheTryCoercedocumentation says so, and that a field holding JSON gives aJsonNode.ClrTypes,SettingsandFieldTypeRegistry.Allare read-only wrappers, so a cast cannot change the sharedFieldTypeRegistry.Core.decimalsettings are also checked at build time, each asFCMS1211:precisionbetween 1 and 28,scalebetween 0 and 28,scalenot aboveprecision.decimalfragment has nomultipleOffor the scale, because validators that compute it in binary floating point reject valid values.eq ne in nin contains startsWith null notNull; integer, decimal, date and dateTime the ordered set withlt lte gt gte between; booleaneq ne null notNull;longTextcontains startsWith null notNull;jsoneq ne contains null notNull(Q7, one level deep);relationeq in null notNull;blockshasBlock null notNull;richTextnull notNull;componentnone (its fields are filtered through their own columns when single).longTextis neither sortable nor indexable;jsonis indexable (GIN on PostgreSQL) but not sortable.text,longText,email,url,slugandselectgivestring(multi selectstring[]),integerlong,decimala normaliseddecimal(no trailing zeros),booleanbool,dateDateOnly,dateTimeaDateTimeOffsetin UTC,richTextaRichText,jsonaJsonNode,relationaGuid(Guid[]when to-many,nullfor an emptyRef<T>),componentaJsonObject(JsonArraywhen repeatable),blocksaJsonArray(aBlockListis kept as is).urlreturns the text, not aUri, because the column and the usual property type are strings; aUriis accepted as input."true"and"false", in any letter case, are the only text read as a boolean.nulland JSONnullalways coerce tonull; whether a value may be empty is a validation rule. Text fordateTimemust be ISO 8601 with a date and a time (2026-11-01T09:12:00Z, an offset, or no offset, which is read as UTC); a date alone is rejected. ADateTimefordatemust be at midnight.decimalhonoursprecisionandscaleby rejecting a value that does not fit, never by rounding.scalelimits the digits after the point,precisionthe significant digits in total; aprecisionwith noscalelimits only the total.richTextcoercion checks that the value is a JSON object of typedocwhosecontent, if present, is an array. It does not run the ProseMirror parser or validator, as asked.richText,json,componentandblocksall also accept a string holding JSON text.componentandblockscoercion checks only the outer shape (an object, a list of objects, each block naming itstype). Checking the fields inside, and that a block is one the field lists, needs the schema model and is left to the content service and Declarative validation, custom validators and serialisable conditions #2685.#/components/schemas/component-{name}and#/components/schemas/block-{name}, because a component and a block may share a name;OpenApiNamesholds the convention for the transformer to reuse. A block field emits aoneOfwith atypediscriminator and mapping.ClrTypeslists property types:Ref<>/RefList<>are open generics;componentlistsobject;selectlistsstringandEnum. Collections of these (List<string>and so on) are not listed.FieldTypeRegistryis public in Core, with a public constructor taking the extras andFieldTypeRegistry.Core. A name can be registered once, so an extra cannot replace a core type and duplicates throwArgumentException.SlugFieldType.Slugify(string source, int? maxLength = null)mapsß æ œ ø đ ð ł ı þto ASCII letters (they have no decomposition), and a source with no Latin letters or digits gives an empty slug. A cut atmaxLengthdrops a trailing hyphen.FieldSettingKindcarries a CA1720 suppression because the members are namedString,IntegerandDecimal.SampleSchema.NewBuildernow registers a testmediadescriptor, andSchemaJsonTestsuses a free-form test type as noted above.dateTimetext with more than 7 fractional digits or an offset without a colon (+0100) is rejected although ISO 8601 allows both; aDateTimeOffsetis rejected fordatewhile aDateTimeat midnight is accepted;SlugifyturnsIt'sintoit-sand gives an empty slug for text with no Latin letters or digits, so the content service needs a fallback when it generates a slug.