Les interfaces Typescript peuvent-elles exprimer des contraintes de cooccurrence pour les propriétés?

Aug 17 2020

Existe-t-il un modèle standard dans une interface Typescript monolithique ou des définitions de type pour affirmer que les propriétés apparaissent ensemble ou n'apparaissent pas du tout?

Par exemple, un élément pourrait être valide s'il ressemblait à ceci ...

{
  id:"ljklkj",
  spellcheck:true,
  spellcheckModel:"byzantine",
}

...ou ca...

{
  id:"ljklkj",
}

Cependant, il serait invalide si l'une des propriétés de vérification orthographique se produisait isolément.

{
  id:"ljklkj",
  spellcheckModel:"byzantine",
}
{
  id:"ljklkj",
  spellcheck:true,
}

Monolithique

Bien sûr, le cas simple ci-dessus pourrait être résolu en créant un Data et un SpellcheckData Type ou Interface. Dans mon cas d'application, cependant, il y aura plus d'un «cluster» de propriétés concomitantes. Définir un nouveau type pour chaque combinaison de cooccurrence conduirait à une explosion de types afin d'exprimer le cas.

Pour cette raison, j'ai qualifié la solution d'interface «monolithique». Bien entendu, il peut être nécessaire d'utiliser une forme de composition pour la définir.

Ce que j'ai essayé

J'ai essayé de trouver des exemples comme celui-ci dans la référence du langage Typescript, mais sans savoir comment la fonctionnalité pourrait être appelée (ou même si c'est une fonctionnalité qui peut être exprimée du tout), j'ai du mal. Les propriétés peuvent être individuellement facultatives mais je ne vois pas de moyen d'exprimer la cooccurrence.

Technologies connexes

Une fonctionnalité équivalente pour la validation des données XML est discutée ici ... https://www.w3.org/wiki/Co-occurrence_constraints

Pour JSON, je comprends que les langages de schéma tels que Schematron et Json Content Rules sont capables d'exprimer des co-contraintes.

Exemple travaillé

Si je devais imaginer une syntaxe dactylographiée pour le cas de co-contrainte appliquée aux ensembles de paramètres HTTP pour le moteur de recherche Solr, cela pourrait ressembler à ceci, indiquant que vous pouvez choisir de satisfaire pleinement les paramètres Spell ou Group, ou pas du tout - une union dans laquelle chaque type est facultatif (indiqué par le ? ) ...

type SolrPassthru =
  SolrCoreParams & (
    SolrSpellParams? |
    SolrGroupParams?  
  )

Cela contraste avec l'exemple ci-dessous, qui est, à mon avis, correct Typecript, mais nécessite TOUS les paramètres de chaque groupe de paramètres.

type SolrCoreParams = {
  defType: SolrDefType,
  boost: SolrBoostType,
}

type SolrSpellParams = {
  spellcheck: "true" | "false",
  "spellcheck.collate": "true" | "false",
  "spellcheck.maxCollationTries": 1,
}

type SolrGroupParams = {
  group: "true" | "false",
  "group.limit": '4'
  "group.sort": 'group_level asc,score desc,published desc,text_sort asc'
  "group.main": 'true'
  "group.field": 'group_uri'
}

type SolrPassthru =
  SolrCoreParams & 
  SolrSpellParams &
  SolrGroupParams

Réponses

3 Lesiak Aug 17 2020 at 15:45

S'il vous plaît essayez ce qui suit. Il semble qu'il montre des erreurs aux bons endroits.

type None<T> = {[K in keyof T]?: never}
type EitherOrBoth<T1, T2> = T1 & None<T2> | T2 & None<T1> | T1 & T2

interface Data {
  id: string;
}

interface SpellCheckData {
  spellcheck: boolean,
  spellcheckModel: string,
}

// Two interfaces
var z1: EitherOrBoth<Data, SpellCheckData> = { id: "" };
var z2: EitherOrBoth<Data, SpellCheckData> = { spellcheck: true,  spellcheckModel: 'm'};
var z3ERROR: EitherOrBoth<Data, SpellCheckData> = { spellcheck: true};
var z4: EitherOrBoth<Data, SpellCheckData> = { id: "", spellcheck: true,  spellcheckModel: 'm'};

interface MoreData {
  p1: string,
  p2: string,
  p3: string,
}

type Monolith = EitherOrBoth<Data, EitherOrBoth<SpellCheckData, MoreData>>

var x1: Monolith  = { id: "" };
var x2: Monolith  = { spellcheck: true,  spellcheckModel: 'm'};
var x3ERROR: Monolith  = { spellcheck: true};                       
var x4: Monolith  = { id: "", spellcheck: true,  spellcheckModel: 'm'};
var x5ERROR: Monolith  = { p1: ""};                                  
var x6ERROR: Monolith  = { p1: "", p2: ""};
var x7: Monolith  = { p1: "", p2: "", p3: ""};
var x8: Monolith  = { id: "", p1: "", p2: "", p3: ""};
var x9ERROR: Monolith  = { id: "", spellcheck: true, p1: "", p2: "", p3: ""};
var x10: Monolith  = { id: "", spellcheck: true, spellcheckModel: 'm', p1: "", p2: "", p3: ""};

Lien Playground

Mettre à jour

Si vous préférez passer des types sous forme de tuple, vous pouvez utiliser l'utilitaire suivant:

type CombinationOf<T> = T extends [infer U1, infer U2] ? EitherOrBoth<U1, U2> :
                        T extends [infer U1, infer U2, infer U3] ? EitherOrBoth<U1, EitherOrBoth<U2, U3>> :
                        T extends [infer U1, infer U2, infer U3, infer U4] ? EitherOrBoth<U1, EitherOrBoth<U2, EitherOrBoth<U3, U4>>> :
                        never;

type Monolith = CombinationOf<[Data, SpellCheckData, MoreData]>

Si certaines propriétés sont requises:

type Monolith = Data & CombinationOf<[Data, SpellCheckData, MoreData]>