Skip to content

Commit

Permalink
README
Browse files Browse the repository at this point in the history
  • Loading branch information
jkbrzt committed Aug 26, 2023
1 parent 702c9f2 commit 5a04108
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 25 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ npm install crosshash
### Python

```python
from crosshash import crossjson, crosshash
from crosshash import crossjson, crosshash, CrossHashError, MAX_SAFE_INTEGER

data = {
'B': 2,
Expand All @@ -45,20 +45,25 @@ data = {
assert crossjson(data) == '{"A":1,"B":2,"C":[1,2,3]}' # stable JSON
assert crosshash(data) == '12982c60a9a8829ea4eeb2e1e7e1e04e' # stable hash

crosshash({'A': MAX_SAFE_INTEGER + 1}) # throws CrossHashError

```

### JavaScript

```javascript
const { crossjson, crosshash } = require('crosshash')
const {crossjson, crosshash, CrossHashError} = require('crosshash')

const data = {
B: 2,
C: [1, 2, 3],
A: 1,
const obj = {
B: 2,
C: [1, 2, 3],
A: 1,
}
assert(crossjson(data) === '{"A":1,"B":2,"C":[1,2,3]}') // stable JSON
assert(crosshash(data) === '12982c60a9a8829ea4eeb2e1e7e1e04e') // stable hash
assert(crossjson(obj) === '{"A":1,"B":2,"C":[1,2,3]}') // stable JSON
assert(crosshash(obj) === '12982c60a9a8829ea4eeb2e1e7e1e04e') // stable hash


crosshash({a: Number.MAX_SAFE_INTEGER + 1}) // throws CrossHashError

```

Expand Down
27 changes: 21 additions & 6 deletions crosshash.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
const MD5 = require('md5.js')
const stringify = require('json-stable-stringify')

const ERROR_UNSAFE_NUMBER = 'ERROR_UNSAFE_NUMBER'

class CrossHashError extends Error {
constructor(message) {
super(message)
this.name = CrossHashError.name
}
}

const crosshash = (obj) => {
return hashString(crossjson(obj))
return md5(crossjson(obj))
}

const crossjson = (obj) => {
return stringify(obj, {replacer: safeIntegerReplacer})
return stringify(obj, {replacer: replacer})
}

const hashString = (string) => {
const md5 = (string) => {
return new MD5().update(string).digest('hex')
}

const safeIntegerReplacer = (key, value) => {
if (typeof value === 'number' && !Number.isSafeInteger(value)) {
throw new Error(`ERROR_UNSAFE_NUMBER: ${value}`)
const replacer = (key, value) => {
if (typeof value === 'number') {
validateNumber(value)
}
return value
}

const validateNumber = (value) => {
if (!Number.isSafeInteger(value)) {
throw new CrossHashError(`${ERROR_UNSAFE_NUMBER}: ${value}`)
}
}

module.exports = {
crosshash,
crossjson,
CrossHashError,
}
11 changes: 5 additions & 6 deletions crosshash.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
import json


__all__ = ['crossjson', 'crosshash', 'UnableToHashStablyError']
__all__ = ['crossjson', 'crosshash', 'CrossHashError']

# <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER>
JS_MAX_SAFE_INTEGER = 9007199254740991 # 2**53 - 1

MAX_SAFE_INTEGER = 9007199254740991 # 2**53 - 1
ERROR_UNSAFE_NUMBER = 'ERROR_UNSAFE_NUMBER'


class UnableToHashStablyError(ValueError):
class CrossHashError(ValueError):
pass


Expand Down Expand Up @@ -51,12 +50,12 @@ def is_safe_number(value: int | float) -> bool:
Return True if value can be hashed safely across JS/Python.
"""
return -JS_MAX_SAFE_INTEGER <= value <= JS_MAX_SAFE_INTEGER
return -MAX_SAFE_INTEGER <= value <= MAX_SAFE_INTEGER


def validate_number(value: int | float):
if not is_safe_number(value):
raise UnableToHashStablyError(f'{ERROR_UNSAFE_NUMBER}: {value}')
raise CrossHashError(f'{ERROR_UNSAFE_NUMBER}: {value}')


def clean_float(value: float):
Expand Down
10 changes: 5 additions & 5 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from . import gen
# noinspection PyProtectedMember
from crosshash import JS_MAX_SAFE_INTEGER, ERROR_UNSAFE_NUMBER
from crosshash import MAX_SAFE_INTEGER, ERROR_UNSAFE_NUMBER


HERE = Path(__file__).parent
Expand All @@ -26,9 +26,9 @@
MAX_STR_LEN = 10
GENERATORS = [
partial(gen.random_string, max_len=MAX_STR_LEN),
partial(gen.random_int, max_val=JS_MAX_SAFE_INTEGER),
partial(gen.random_int, max_val=MAX_SAFE_INTEGER),
gen.random_bool,
partial(gen.random_float, max_size=JS_MAX_SAFE_INTEGER),
partial(gen.random_float, max_size=MAX_SAFE_INTEGER),
]
GENERATORS = [
*GENERATORS,
Expand All @@ -43,8 +43,8 @@
]

CASES_UNSAFE_NUMBERS = [
{'unsafe_int': JS_MAX_SAFE_INTEGER + 1},
{'unsafe_float': JS_MAX_SAFE_INTEGER + 1.1},
{'unsafe_int': MAX_SAFE_INTEGER + 1},
{'unsafe_float': MAX_SAFE_INTEGER + 1.1},
]


Expand Down

0 comments on commit 5a04108

Please sign in to comment.