From cdbc0dcd1640240a919bd01a31988a22841bdb4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Wed, 24 Jun 2020 16:02:42 +0200
Subject: [PATCH] [ivette] fix in decoders

---
 ivette/api/server_tsc.ml                  |   2 +-
 ivette/src/dome/src/renderer/data/json.ts | 103 +++++++++++++++++-----
 2 files changed, 83 insertions(+), 22 deletions(-)

diff --git a/ivette/api/server_tsc.ml b/ivette/api/server_tsc.ml
index a50958b176c..378dfc49d94 100644
--- a/ivette/api/server_tsc.ml
+++ b/ivette/api/server_tsc.ml
@@ -168,7 +168,7 @@ let rec makeDecoder ~safe ?self ~names fmt js =
   | Jself -> jcall names fmt (Pkg.Derived.decode ~safe (getSelf self))
   | Joption js -> makeLoose fmt js
   | Jdict(kd,js) ->
-    Format.fprintf fmt "@[<hov 2>Json.jDict('#%s',@,%a)@]" kd makeLoose js
+    Format.fprintf fmt "@[<hov 2>Json.jDictionary('#%s',@,%a)@]" kd makeLoose js
   | Jlist js ->
     Format.fprintf fmt "@[<hov 2>Json.jList(%a)@]" makeLoose js
   | Jarray js ->
diff --git a/ivette/src/dome/src/renderer/data/json.ts b/ivette/src/dome/src/renderer/data/json.ts
index 2e28501d599..7339577a393 100644
--- a/ivette/src/dome/src/renderer/data/json.ts
+++ b/ivette/src/dome/src/renderer/data/json.ts
@@ -71,6 +71,12 @@ export function identity<A>(v: A): A { return v; };
 // --- Primitives
 // --------------------------------------------------------------------------
 
+/** Always returns `undefined` on any input. */
+export const jNull: Safe<undefined> = (_: json) => undefined;
+
+/** Identity. */
+export const jAny: Safe<json> = (js: json) => js;
+
 /** Primitive JSON number or `undefined`. */
 export const jNumber: Loose<number> = (js: json) => (
   typeof js === 'number' && !Number.isNaN(js) ? js : undefined
@@ -106,13 +112,21 @@ export const jString: Loose<string> = (js: json) => (
   typeof js === 'string' ? js : undefined
 );
 
+/**
+   Lookup tags in a dictionary.
+   Can be used directly for enum types, eg. `jEnum(myEnumType)`.
+ */
+export function jEnum<A>(d: { [tag: string]: A }): Loose<A> {
+  return (v: json) => typeof v === 'string' ? d[v] : undefined;
+}
+
 /**
    One of the enumerated _constants_ or `undefined`.
    The typechecker will prevent you from listing values that are not in
    type `A`. However, it will not protected you
    from missings constants in `A`.
 */
-export function jEnum<A>(...values: ((string | number) & A)[]): Loose<A> {
+export function jTags<A>(...values: ((string | number) & A)[]): Loose<A> {
   var m = new Map<string | number, A>();
   values.forEach(v => m.set(v, v));
   return (v: json) => (typeof v === 'string' ? m.get(v) : undefined);
@@ -131,18 +145,10 @@ export function jDefault<A>(
 }
 
 /**
-   Force returning `undefined` or a default value for `undefined` JSON input.
+   Force returning `undefined` or a default value for `undefined` _or_ `null` JSON input.
    Typically usefull to leverage an existing `Safe<A>` decoder.
  */
 export function jOption<A>(fn: Safe<A>, defaultValue?: A): Loose<A> {
-  return (js: json) => (js === undefined ? defaultValue : fn(js));
-}
-
-/**
-   Force returning `undefined` or a default value for `undefined` _or_ `null`
-   JSON input. Typically usefull to leverage an existing `Safe<A>` decoder.
- */
-export function jNull<A>(fn: Safe<A>, defaultValue?: A): Loose<A> {
   return (js: json) => (js === undefined || js === null ? defaultValue : fn(js));
 }
 
@@ -349,6 +355,19 @@ export function jObject<A>(fp: Props<A>): Loose<A> {
   };
 }
 
+/**
+   Returns the first decoder result that is not undefined.
+ */
+export function jUnion<A>(...cases: Loose<A>[]): Loose<A> {
+  return (js: json) => {
+    for (var fn of cases) {
+      const fv = fn(js);
+      if (fv !== undefined) return fv;
+    }
+    return undefined;
+  };
+}
+
 /**
    Encoders for each property of object type `A`.
 */
@@ -378,22 +397,64 @@ export function eObject<A>(fp: EProps<A>): Encoder<A> {
   }
 }
 
-/** Type of dictionaries. */
-export type dict<A> = { [key: string]: A };
+// Intentionnaly internal and only declared
+declare const tag: unique symbol;
+
+/** Phantom type. */
+export type phantom<K, A> = A & { tag: K };
+
+export function forge<K, A>(_tag: K, data: A): phantom<K, A> {
+  return data as any;
+}
+
+/** String key with kind. Can be used as a `string` but shall be created with [forge]. */
+export type key<K> = phantom<K, string>;
+
+/** Number index with kind. Can be used as a `number` but shall be created with [forge]. */
+export type index<K> = phantom<K, number>;
+
+/** Decoder for `key<K>` strings. */
+export function jKey<K>(kd: K): Loose<key<K>> {
+  return (js: json) => typeof js === 'string' ? forge(kd, js) : undefined;
+}
+
+/** Decoder for `index<K>` numbers. */
+export function jIndex<K>(kd: K): Loose<index<K>> {
+  return (js: json) => typeof js === 'number' ? forge(kd, js) : undefined;
+}
+
+/** Dictionaries with « typed » keys. */
+export type dict<K, A> = phantom<K, { [key: string]: A }>
+
+/** Lookup into dictionary.
+    Better than a direct access to `d[k]` for undefined values. */
+export function lookup<K, A>(d: dict<K, A>, k: key<K>): A | undefined {
+  return d[k];
+}
+
+/** Empty dictionary. */
+export function empty<K, A>(kd: K): dict<K, A> {
+  return forge(kd, {} as any);
+}
+
+/** Dictionary extension. */
+export function index<K, A>(d: dict<K, A>, key: key<K>, value: A) {
+  d[key] = value;
+}
 
 /**
    Decode a JSON dictionary, dicarding all inconsistent entries.
    If the JSON contains no valid entry, still returns `{}`.
 */
-export function jDictionary<A>(fn: Loose<A>): Safe<dict<A>> {
+export function jDictionary<K, A>(kd: K, fn: Loose<A>): Safe<dict<K, A>> {
   return (js: json) => {
-    const buffer: dict<A> = {};
+    const buffer: dict<K, A> = empty(kd);
     if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
-      for (var k of Object.keys(js)) {
-        const fd = js[k];
+      for (var key of Object.keys(js)) {
+        const fd = js[key];
         if (fd !== undefined) {
           const fv = fn(fd);
-          if (fv !== undefined) buffer[k] = fv;
+          if (fv !== undefined) index(buffer, forge(kd, key), fv);
         }
       }
     }
@@ -405,14 +466,14 @@ export function jDictionary<A>(fn: Loose<A>): Safe<dict<A>> {
    Encode a dictionary into JSON, dicarding all inconsistent entries.
    If the dictionary contains no valid entry, still returns `{}`.
 */
-export function eDictionary<A>(fn: Encoder<A>): Encoder<dict<A>> {
-  return (d: dict<A>) => {
+export function eDictionary<K, A>(fn: Encoder<A>): Encoder<dict<K, A>> {
+  return (d: dict<K, A>) => {
     const js: json = {};
     for (var k of Object.keys(d)) {
       const fv = d[k];
       if (fv !== undefined) {
-        const fv = fn(d[k]);
-        if (fv !== undefined) js[k] = fv;
+        const fr = fn(fv);
+        if (fr !== undefined) js[k] = fr;
       }
     }
     return js;
-- 
GitLab