Skip to content

Instantly share code, notes, and snippets.

@gmmorris
Created May 16, 2018 19:56
Show Gist options
  • Save gmmorris/2b132aaeafe2265dd8a2b95bafa0f3b4 to your computer and use it in GitHub Desktop.
Save gmmorris/2b132aaeafe2265dd8a2b95bafa0f3b4 to your computer and use it in GitHub Desktop.
Bucklescript bindings approaches for complex Object based arguments
var CryptographyJs = require("./cryptography.js");
CryptographyJs.hash({ algorithm: 'md5', length: 10 }, "asd");
CryptographyJs.hash({ algorithm: 'md5' }, "asd");
CryptographyJs.hash({ length: 10 }, "asd");
CryptographyJs.hash({}, "asd");
Cryptography.hashWithOpts(hashOptions(~algorithm=`MD5, ~length=10, ()), "asd");
Cryptography.hashWithOpts(hashOptions(~algorithm=`MD5, ()), "asd");
Cryptography.hashWithOpts(hashOptions(~length=10, ()), "asd");
Cryptography.hashWithOpts(hashOptions(), "111");
[@bs.deriving jsConverter]
type hash = [
| [@bs.as "sha256"] `SHA256
| [@bs.as "sha512"] `SHA512
| [@bs.as "sha384"] `SHA384
| [@bs.as "sha1"] `SHA1
| [@bs.as "md5"] `MD5
];
[@bs.deriving abstract]
type hashConfig = {
[@bs.optional]
algorithm: string,
[@bs.optional]
length: int,
};
module Crypt_ = {
[@bs.obj]
external hashOptions :
(~algorithm: Js.Nullable.t(string), ~length: Js.Nullable.t(int), unit) =>
hashConfig =
"";
};
let mapNullable = (opt: option('a), cb: 'a => 'b) =>
switch (opt) {
| Some(a) => Js.Nullable.return(cb(a))
| None => Js.Nullable.undefined
};
let hashOptions = (~algorithm=?, ~length=?, ()) =>
Crypt_.hashOptions(
~algorithm=mapNullable(algorithm, hashToJs),
~length=mapNullable(length, i => i),
(),
);
[@bs.module "./cryptography.js"]
external hashWithOpts : (hashConfig, string) => unit = "hash";
hashWithOpts(hashOptions(~algorithm=`MD5, ~length=10, ()), "asd");
hashWithOpts(hashOptions(~algorithm=`MD5, ()), "asd");
hashWithOpts(hashOptions(~length=10, ()), "asd");
hashWithOpts(hashOptions(), "asd");
{ algorithm: 'md5', length: 10 } 'asd'
{ algorithm: 'md5', length: undefined } 'asd'
{ algorithm: undefined, length: 10 } 'asd'
{ algorithm: undefined, length: undefined } 'asd'
[@bs.deriving jsConverter]
type hash = [
| [@bs.as "sha256"] `SHA256
| [@bs.as "sha512"] `SHA512
| [@bs.as "sha384"] `SHA384
| [@bs.as "sha1"] `SHA1
| [@bs.as "md5"] `MD5
];
[@bs.deriving abstract]
type hashConfig = {
[@bs.optional]
algorithm: string,
[@bs.optional]
length: int,
};
module Crypt_ = {
[@bs.obj]
external hashOptions :
(~algorithm: string=?, ~length: int=?, unit) => hashConfig =
"";
};
let mapNullable = (opt: option('a), cb: 'a => 'b) =>
switch (opt) {
| Some(a) => Js.Nullable.return(cb(a))
| None => Js.Nullable.undefined
};
let hashOptions = (~algorithm=?, ~length=?, ()) =>
switch (algorithm) {
| Some(algorithm_) =>
switch (length) {
| Some(length_) =>
Crypt_.hashOptions(
~algorithm=hashToJs(algorithm_),
~length=length_,
(),
)
| None => Crypt_.hashOptions(~algorithm=hashToJs(algorithm_), ())
}
| None =>
switch (length) {
| Some(length_) => Crypt_.hashOptions(~length=length_, ())
| None => Crypt_.hashOptions()
}
};
[@bs.module "./cryptography.js"]
external hashWithOpts : (hashConfig, string) => unit = "hash";
hashWithOpts(hashOptions(~algorithm=`MD5, ~length=10, ()), "asd");
hashWithOpts(hashOptions(~algorithm=`MD5, ()), "asd");
hashWithOpts(hashOptions(~length=10, ()), "asd");
hashWithOpts(hashOptions(), "asd");
{ algorithm: 'md5', length: 10 } 'asd'
{ algorithm: 'md5' } 'asd'
{ length: 10 } 'asd'
{} 'asd'
@gmmorris
Copy link
Author

gmmorris commented May 19, 2018

OK, this has been very helpful but I'm hitting another wall.
And sorry for asking so many questions but I can't seem to find detailed documentation of these features beyond the Bucklescript manual (https://bucklescript.github.io/bucklescript/Manual.html) which while detailed in general, is quite sparse on the side effects of these annotations (gives many different examples, but each example is quite basic).

The one issue I'm still stuck on is that I actually got the TheAPIThatIWantToSupport.re a little wrong.
Sadly the API of the JS library isn't quite compatible with Reason's semantics, and some of the keys are actually capitalised.
If we apply this to our contrived example above, it would be something like:

var CryptographyJs = require("./cryptography.js");

CryptographyJs.hash({ Algorithm: 'md5', length: 10 }, "asd");
CryptographyJs.hash({ Algorithm: 'md5' }, "asd");
CryptographyJs.hash({ length: 10 }, "asd");
CryptographyJs.hash({}, "asd");

See how the Algorithm field is capitalised?
I've tried explicitly mentioning the algorithm field with a [@bs.as] annotation on the hashConfig but that didn't work.

I tried using a deriving like this:

[@bs.deriving jsConverter]
type hashConfig = {
  [@bs.optional]
  _Algorithm: string,
  [@bs.optional]
  length: int,
};

The _ seems to tell it to convert to a capitalised property, which is good, but I can't figure out how to pass that through to the hashWithOpts without explicitly changing the signature to {. "_Algorithm": string, "length": int} - and that had a weird result because the [bs.obj] seems to return an object rather than the array that the generated hashConfigToJs expects.
I seem to have made a mess of the whole thing . :p

Any ideas?

@gmmorris
Copy link
Author

A...nd got there. Honestly don't get how that wasn't obvious to me. facepalm

type hashConfig;

[@bs.obj]
external hashOptions :
  (~_Algorithm: string=?, ~length: int=?, unit) => hashConfig =
  "";

let hashOptions = (~algorithm=?, ~length=?, ()) =>
  hashOptions(
    ~_Algorithm=?Belt.Option.map(algorithm, hashToJs),
    ~length?,
    (),
  );

Cheers :D

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