Skip to content

Commit

Permalink
Added UTF8 range condition
Browse files Browse the repository at this point in the history
  • Loading branch information
joente committed Feb 1, 2024
1 parent f98de4e commit 8a47c74
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* The `new_backup()` and `new_token()` functions no longer accept `int`, `float` or `str` as time.
_(This was marked as deprecated since v0.10.1)_
* Added set operators `<=`, `<`, `>=`, `>` for subset, proper subset, superset and proper superset checking.
* Added range support for UTF8 definition.

# v1.4.16

Expand Down
1 change: 1 addition & 0 deletions inc/ti/spec.t.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ typedef enum
TI_SPEC_INT_RANGE, /* `int<:> */
TI_SPEC_FLOAT_RANGE, /* `float<:> */
TI_SPEC_STR_RANGE, /* `str<:> */
TI_SPEC_UTF8_RANGE, /* `utf8<:> */
} ti_spec_enum_t;

typedef enum
Expand Down
2 changes: 1 addition & 1 deletion inc/ti/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* "-rc0"
* ""
*/
#define TI_VERSION_PRE_RELEASE "-alpha11"
#define TI_VERSION_PRE_RELEASE "-alpha12"

#define TI_MAINTAINER \
"Jeroen van der Heijden <jeroen@cesbit.com>"
Expand Down
101 changes: 98 additions & 3 deletions itest/test_advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,14 @@ async def test_conditions(self, client):
set_type('Foo', {a: 'str<-1:5>'});
''')

with self.assertRaisesRegex(
ValueError,
r'invalid declaration for `a` on type `Foo`; '
r'the minimum value for a string range must not be negative'):
await client.query(r'''
set_type('Foo', {a: 'utf8<-1:5>'});
''')

with self.assertRaisesRegex(
ValueError,
r'invalid declaration for `a` on type `Foo`; '
Expand Down Expand Up @@ -636,6 +644,10 @@ async def test_conditions(self, client):
str_i: '/^(e|h|i|l|o){2}$/i<Hi>',
str_j: 'str<5:10:empty>?',
str_k: 'str<5:5>?',
utf8_a: 'utf8<0:1>',
utf8_b: 'utf8<1:1>',
utf8_c: 'utf8<3:10>',
utf8_d: 'utf8<0:10:unknown>',
});
Foo();
Expand Down Expand Up @@ -666,6 +678,10 @@ async def test_conditions(self, client):
"str_i": "Hi",
"str_j": "empty",
"str_k": None,
"utf8_a": "",
"utf8_b": "-",
"utf8_c": "---",
"utf8_d": "unknown",
})

self.assertEqual(await client.query(r'''
Expand Down Expand Up @@ -772,7 +788,7 @@ async def test_conditions(self, client):
ValueError,
r'mismatch in type `Foo`; '
r'property `str_b` requires a string with a length '
r'of 1 character'):
r'of 1'):
await client.query(r'''
Foo{str_b: ""};
''')
Expand All @@ -781,7 +797,7 @@ async def test_conditions(self, client):
ValueError,
r'mismatch in type `Foo`; '
r'property `str_c` requires a string with a length '
r'between 3 and 10 \(both inclusive\) characters'):
r'between 3 and 10 \(both inclusive\)'):
await client.query(r'''
Foo{str_c: "xx"};
''')
Expand All @@ -790,7 +806,7 @@ async def test_conditions(self, client):
ValueError,
r'mismatch in type `Foo`; '
r'property `str_k` requires a string with a length '
r'of 5 characters'):
r'of 5'):
await client.query(r'''
Foo{str_k: "ABCDEF"};
''')
Expand Down Expand Up @@ -2498,5 +2514,84 @@ async def test_future_name(self, client):
user = add_user(); user.id();
""")

async def test_utf8_range(self, client):
await client.query(r"""//ti
set_type('A', {
u: 'utf8'
});
set_type('B', {
u: 'utf8<1:>'
});
set_type('C', {
u: 'utf8<:2>'
});
set_type('D', {
u: 'utf8<1:3>'
});
set_type('E', {
u: 'utf8<2:2>'
});
""")
with self.assertRaisesRegex(
ValueError,
r'mismatch in type `B`; property `u` requires a '
r'string with a length of at least 1'):
await client.query(r"""//ti
B{u: ""};
""")
with self.assertRaisesRegex(
ValueError,
r'mismatch in type `C`; property `u` requires a string '
r'with a length between 0 and 2 \(both inclusive\)'):
await client.query(r"""//ti
C{u: "aaa"};
""")
with self.assertRaisesRegex(
ValueError,
r'mismatch in type `D`; property `u` requires a string '
r'with a length between 1 and 3 \(both inclusive\)'):
await client.query(r"""//ti
D{u: ""};
""")
with self.assertRaisesRegex(
ValueError,
r'mismatch in type `E`; property `u` requires a '
r'string with a length of 2'):
await client.query(r"""//ti
E{u: ""};
""")
with self.assertRaisesRegex(
ValueError,
r'mismatch in type `B`; property `u` only accepts '
r'valid UTF8 data'):
await client.query(r"""//ti
B{u: "😁"[:3]};
""")

res = await client.query(r"""//ti
a = A{u: "A"};
a.wrap('B')
""")
self.assertEqual(res, {})

res = await client.query(r"""//ti
b = B{u: "B"};
b.wrap('A')
""")
self.assertEqual(res, {"u": "B"})

res = await client.query(r"""//ti
b = B{u: "B"};
b.wrap('D')
""")
self.assertEqual(res, {})

res = await client.query(r"""//ti
d = D{u: "D"};
d.wrap('B')
""")
self.assertEqual(res, {"u": "D"})


if __name__ == '__main__':
run_test(TestAdvanced())
3 changes: 1 addition & 2 deletions itest/test_collection_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5425,8 +5425,7 @@ async def test_to_type(self, client):
with self.assertRaisesRegex(
ValueError,
r'mismatch in type `B`; property `name` requires a string '
r'with a length between 3 and 20 \(both inclusive\) '
r'characters'):
r'with a length between 3 and 20 \(both inclusive\)'):
await client.query('.aa.pop(); .to_type("B");')

with self.assertRaisesRegex(
Expand Down
8 changes: 8 additions & 0 deletions src/ti/condition.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ int ti_condition_field_info_init(
str += 2;
break;
}
if (n > 4 && memcmp(str, "tf8", 3) == 0)
{
spec = TI_SPEC_UTF8_RANGE;
str += 3;
break;
}
goto invalid;
case 't':
++str;
Expand Down Expand Up @@ -158,6 +164,7 @@ int ti_condition_field_info_init(
return 0;
}
case TI_SPEC_STR_RANGE:
case TI_SPEC_UTF8_RANGE:
{
int64_t ma, mi = strx_to_int64(str, &tmp);

Expand Down Expand Up @@ -697,6 +704,7 @@ void ti_condition_destroy(ti_condition_via_t condition, uint16_t spec)
ti_val_drop((ti_val_t *) condition.re->regex);
/* fall through */
case TI_SPEC_STR_RANGE:
case TI_SPEC_UTF8_RANGE:
case TI_SPEC_INT_RANGE:
case TI_SPEC_FLOAT_RANGE:
ti_val_drop(condition.none->dval);
Expand Down
80 changes: 42 additions & 38 deletions src/ti/field.c
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,17 @@ int ti_field_make_assignable(
((ti_raw_t *) *val)->n > field->condition.srange->ma)
goto srange_error;
return 0;
case TI_SPEC_UTF8_RANGE:
if (!ti_val_is_str(*val))
goto type_error;
if (!strx_is_utf8n(
(const char *) ((ti_raw_t *) *val)->data,
((ti_raw_t *) *val)->n))
goto utf8_error;
if (((ti_raw_t *) *val)->n < field->condition.srange->mi ||
((ti_raw_t *) *val)->n > field->condition.srange->ma)
goto srange_error;
return 0;
}

assert(spec >= TI_ENUM_ID_FLAG);
Expand Down Expand Up @@ -1884,20 +1895,27 @@ int ti_field_make_assignable(
return e->nr;

srange_error:
if (field->condition.srange->mi == field->condition.srange->ma)
if (field->condition.srange->ma == INT64_MAX)
ex_set(e, EX_VALUE_ERROR,
"mismatch in type `%s`; "
"property `%s` requires a string with a length of at least "
"%zu",
field->type->name,
field->name->str,
field->condition.srange->mi);
else if (field->condition.srange->mi == field->condition.srange->ma)
ex_set(e, EX_VALUE_ERROR,
"mismatch in type `%s`; "
"property `%s` requires a string with a length "
"of %zu character%s",
"of %zu",
field->type->name,
field->name->str,
field->condition.srange->mi,
field->condition.srange->mi == 1 ? "": "s");
field->condition.srange->mi);
else
ex_set(e, EX_VALUE_ERROR,
"mismatch in type `%s`; "
"property `%s` requires a string with a length "
"between %zu and %zu (both inclusive) characters",
"between %zu and %zu (both inclusive)",
field->type->name,
field->name->str,
field->condition.srange->mi,
Expand Down Expand Up @@ -2096,6 +2114,12 @@ _Bool ti_field_maps_to_val(ti_field_t * field, ti_val_t * val)
return (ti_val_is_str(val) &&
((ti_raw_t *) val)->n >= field->condition.srange->mi &&
((ti_raw_t *) val)->n <= field->condition.srange->ma);
case TI_SPEC_UTF8_RANGE:
return (ti_val_is_str(val) && strx_is_utf8n(
(const char *) ((ti_raw_t *) val)->data,
((ti_raw_t *) val)->n) &&
((ti_raw_t *) val)->n >= field->condition.srange->mi &&
((ti_raw_t *) val)->n <= field->condition.srange->ma);
}

/* any *thing* can be mapped */
Expand Down Expand Up @@ -2186,6 +2210,7 @@ static _Bool field__maps_to_nested(ti_field_t * t_field, ti_field_t * f_field)
case TI_SPEC_INT_RANGE:
case TI_SPEC_FLOAT_RANGE:
case TI_SPEC_STR_RANGE:
case TI_SPEC_UTF8_RANGE:
return false;
}

Expand All @@ -2196,36 +2221,6 @@ static _Bool field__maps_to_nested(ti_field_t * t_field, ti_field_t * f_field)
ti_spec_is_set(f_field->spec);
}

_Bool field__maps_with_condition(ti_field_t * t_field, ti_field_t * f_field)
{
uint16_t spec = t_field->spec & TI_SPEC_MASK_NILLABLE;
assert(t_field->condition.none);
assert(f_field->condition.none);

switch((ti_spec_enum_t) spec)
{
case TI_SPEC_REMATCH:
return ti_regex_eq(
t_field->condition.re->regex,
f_field->condition.re->regex);
case TI_SPEC_STR_RANGE:
return (
t_field->condition.srange->mi <= f_field->condition.srange->mi &&
t_field->condition.srange->ma >= f_field->condition.srange->ma);
case TI_SPEC_INT_RANGE:
return (
t_field->condition.irange->mi <= f_field->condition.irange->mi &&
t_field->condition.irange->ma >= f_field->condition.irange->ma);
case TI_SPEC_FLOAT_RANGE:
return (
t_field->condition.drange->mi <= f_field->condition.drange->mi &&
t_field->condition.drange->ma >= f_field->condition.drange->ma);
default:
assert(0);
return false;
}
}

_Bool ti_field_maps_to_field(ti_field_t * t_field, ti_field_t * f_field)
{
uint16_t t_spec, f_spec;
Expand Down Expand Up @@ -2270,16 +2265,20 @@ _Bool ti_field_maps_to_field(ti_field_t * t_field, ti_field_t * f_field)
f_spec == TI_SPEC_URL ||
f_spec == TI_SPEC_TEL ||
f_spec == TI_SPEC_REMATCH ||
f_spec == TI_SPEC_STR_RANGE);
f_spec == TI_SPEC_STR_RANGE ||
f_spec == TI_SPEC_UTF8_RANGE);
case TI_SPEC_STR:
return (f_spec == TI_SPEC_STR ||
f_spec == TI_SPEC_UTF8 ||
f_spec == TI_SPEC_EMAIL ||
f_spec == TI_SPEC_URL ||
f_spec == TI_SPEC_TEL ||
f_spec == TI_SPEC_REMATCH ||
f_spec == TI_SPEC_STR_RANGE);
f_spec == TI_SPEC_STR_RANGE ||
f_spec == TI_SPEC_UTF8_RANGE);
case TI_SPEC_UTF8:
return (f_spec == TI_SPEC_UTF8 ||
f_spec == TI_SPEC_UTF8_RANGE);
case TI_SPEC_BYTES:
return f_spec == t_spec;
case TI_SPEC_INT:
Expand Down Expand Up @@ -2351,7 +2350,12 @@ _Bool ti_field_maps_to_field(ti_field_t * t_field, ti_field_t * f_field)
f_field->condition.drange->ma <= t_field->condition.drange->ma);
case TI_SPEC_STR_RANGE:
return (
f_spec == TI_SPEC_STR_RANGE &&
(f_spec == TI_SPEC_STR_RANGE || f_spec == TI_SPEC_UTF8_RANGE) &&
f_field->condition.srange->mi >= t_field->condition.srange->mi &&
f_field->condition.srange->ma <= t_field->condition.srange->ma);
case TI_SPEC_UTF8_RANGE:
return (
f_spec == TI_SPEC_UTF8_RANGE &&
f_field->condition.srange->mi >= t_field->condition.srange->mi &&
f_field->condition.srange->ma <= t_field->condition.srange->ma);
}
Expand Down
Loading

0 comments on commit 8a47c74

Please sign in to comment.