gcanti / tcomb

Type checking and DDD for JavaScript
MIT License
1.89k stars 120 forks source link

Usage question: Can I tcomb-ize keys from an object? #284

Open fernandocanizo opened 7 years ago

fernandocanizo commented 7 years ago

Version

Irrelevant, but I'm using v3.2.20

Expected behaviour

I'd like to define a restricted tree-like structure where:

  1. structure keys can have any value while it's a string
  2. leaf nodes have this form: { [t.String]: t.String }
  3. branch nodes are just a way to group previous ones: { [t.String]: { [t.String]: t.String, ... }
  4. structures is shallow: only two levels deep

An example:

{
  regionLayer: 'region',
  nativeForesLayer: {
    valparaiso: 'v',
    maule: 'm'
  },
  eroLayers: {
    erodabilidad: 'erodabilidad',
    erosividad: 'erosividad'
  }
}

Actual behaviour

I tried this:

'use strict';

const t = require('tcomb');

const Tree = t.declare('Tree');

Tree.define(t.struct({
  [t.String]: t.maybe(t.String),
  [t.String]: t.maybe(Tree),
}));

const test = Tree({
  regionLayer: 'region',
  nativeForesLayer: {
    valparaiso: 'v',
    maule: 'm'
  },
  eroLayers: {
    erodabilidad: 'erodabilidad',
    erosividad: 'erosividad'
  }
});

console.log(test);

Output is:

$ node model.js 
Struct {
  'function Irreducible(value, path) {\n\n    if (process.env.NODE_ENV !== \'production\') {\n      forbidNewOperator(this, Irreducible);\n      path = path || [name];\n      assert(predicate(value), function () { return \'Invalid value \' + assert.stringify(value) + \' supplied to \' + path.join(\'/\'); });\n    }\n\n    return value;\n  }': undefined }
gcanti commented 7 years ago

Could you please provide more examples? It's not clear to me what's the structure of Tree

// leaf nodes have this form: { [t.String]: t.String }
const Leaf = t.dict(t.String, t.String)

// branch nodes are just a way to group previous ones: { [t.String]: { [t.String]: t.String, ... }
const Branch = t.dict(t.String, Leaf)

// structures is shallow: only two levels deep
const Tree = ???
fernandocanizo commented 7 years ago

Oh! I wasn't aware of the t.dict combinator. Also I didn't know the t.subtype combinator which I just found on the guide, but t.refinement is missing from https://gcanti.github.io/tcomb/guide/index.html ... Now I'm not sure which is the source of true regarding tcomb documentation. t.refinement still works.

Anyway, I tried to give a full example but I got stuck before I even reached the point of defining my tree. Let me expand:

I redefined my structure since I asked this question. It's basically a mapping of known keys to names for table names from a database which can change. The mapping allows me to use the same names in code.

Current structures is like below, the optional query field allows me to throw a different query from config if a particular case arise where I need different information than the standard. My tree is for nativeForestLayer.

'use strict';

const t = require('tcomb');

const nativeForestLayer = {
  // Note: keys on this hash come from regionLayer this query:
  // select regexp_replace(lower(nom_reg), '[^a-z]', '_', 'g') as region from datasources.<chile> order by region
  // Ensure you name your keys exactly the same

  aisen_del_general_carlos_ibanez_del_campo: {
    datasource_id: 'aysen_2011_rv_6',
    query: '',
  },
  antofagasta: {
    datasource_id: 'antofagasta_1997_rv_7',
    query: '',
  },
  arica_y_parinacota: {
    datasource_id: 'arica_1997_rv_9',
    query: '',
  },
};

const DbFriendlyName = t.subtype(t.String, x => Boolean(x.match(/[_a-z]+/)), 'DbFriendlyName');

const NativeForestLeaf = t.struct({
  datasource_id: DbFriendlyName,
  query: t.maybe(t.String),
});

const NativeForestBranch = t.dict(DbFriendlyName, NativeForestLeaf, 'NativeForestBranch');

// ??? Don't know how to define the tree
const NativeForestTree = t.dict(DbFriendlyName, t.Object(NativeForestBranch));

//
// test stuff
//

DbFriendlyName('valparaiso_2013_rv_19');

NativeForestLeaf({
  datasource_id: 'valparaiso_2013_rv_19',
  query: '',
});

NativeForestBranch({
  valparaiso: {
    datasource_id: 'valparaiso_2013_rv_19',
    query: '',
  },
});

NativeForestTree(nativeForestLayer);
// TypeError: [tcomb] Invalid value "NativeForestBranch" supplied to Object
gcanti commented 7 years ago

but t.refinement is missing from https://gcanti.github.io/tcomb/guide/index.html

That page is pretty old (tcomb v1.0.0). See https://github.com/gcanti/tcomb/blob/master/docs/API.md#the-refinement-combinator

refinement requires a predicate, this should work

const DbFriendlyName = t.refinement(t.String, x => x.match(/[_a-z]+/) !== null, 'DbFriendlyName');
fernandocanizo commented 7 years ago

Sorry, my original question opened up when things started to not-work. I came here to edit my previous post as I found that

const DbFriendlyName = t.subtype(t.String, x => Boolean(x.match(/[_a-z]+/)), 'DbFriendlyName');

worked.

So, back to my original doubt (aka incompetence): I updated code on my previous post so to not spam the thread. I don't know how to define NativeForestTree.

fernandocanizo commented 7 years ago

On a side note:

  1. t.subtype is the same thing as t.refinement ?
  2. shouldn't you take down https://gcanti.github.io/tcomb/guide/index.html It's confusing to have that documentation up, and I didn't realize it was for an old version.
gcanti commented 7 years ago

shouldn't you take down

good idea

t.subtype is the same thing as t.refinement

yes is an alias

From the provided example I don't understand why you need NativeForestTree: nativeForestLayer is a NativeForestBranch

fernandocanizo commented 7 years ago

So the ...Branch is the ...Tree? ...

Oh! I got it now! I was defining const NativeForestTree = t.dict(DbFriendlyName, t.Object(NativeForestBranch)); (uncommented in my test) and tcomb was exploding for that, but I misread the message and thought tcomb was complaining about NativeForestBranch(nativeForestLayer);

Beautiful! It works now. Thank you very much for your time Giulio.

P.S.: would it be appropriate to rename NativeForestBranch to NativeForestTree then ?