1 #![allow(clippy::too_many_arguments)]
2 /// Tests that are unique to the Rust implementation of oso, testing things like
3 /// rust class handling.
4 use maplit::hashmap;
5 use thiserror::Error;
6
7 use oso::{ClassBuilder, PolarClass};
8
9 mod common;
10
11 use common::OsoTest;
12
13 use std::convert::TryFrom;
14
15 #[test]
test_anything_works()16 fn test_anything_works() {
17 common::setup();
18
19 let mut test = OsoTest::new();
20 test.load_str("f(1);");
21 let results = test.query("f(x)");
22 assert_eq!(results[0].get_typed::<u32>("x").unwrap(), 1);
23 let results = test.query("f(y)");
24 assert_eq!(results[0].get_typed::<u32>("y").unwrap(), 1);
25 }
26
27 #[test]
test_helpers()28 fn test_helpers() {
29 common::setup();
30
31 let mut test = OsoTest::new();
32 test.load_file(file!(), "test_file.polar").unwrap();
33 assert_eq!(
34 test.query("f(x)"),
35 vec![
36 hashmap! { "x" => 1, },
37 hashmap! { "x" => 2, },
38 hashmap! { "x" => 3, },
39 ]
40 );
41 assert_eq!(test.qvar::<u32>("f(x)", "x"), [1, 2, 3]);
42 }
43
44 #[test]
test_data_conversions()45 fn test_data_conversions() {
46 common::setup();
47
48 let mut test = OsoTest::new();
49 test.load_str(
50 r#"
51 a(1);
52 b("two");
53 c(true);
54 d([1, "two", true]);"#,
55 );
56 test.qvar_one("a(x)", "x", 1);
57 test.qvar_one("b(x)", "x", "two".to_string());
58 test.qvar_one("c(x)", "x", true);
59
60 use oso::PolarValue;
61 //use polar_core::terms::Value;
62
63 // TODO: do we want to handle hlists better?
64 // e.g. https://docs.rs/hlist/0.1.2/hlist/
65 let mut results = test.query("d(x)");
66 let first = results.pop().unwrap();
67 let mut x = first.get_typed::<Vec<PolarValue>>("x").unwrap();
68 assert_eq!(i64::try_from(x.remove(0)).unwrap(), 1);
69 assert_eq!(String::try_from(x.remove(0)).unwrap(), "two");
70 assert_eq!(bool::try_from(x.remove(0)).unwrap(), true);
71 }
72
73 // This logic is changing. Updated when fixed
74 #[ignore]
75 #[test]
test_load_function()76 fn test_load_function() {
77 common::setup();
78
79 let mut test = OsoTest::new();
80 test.load_file(file!(), "test_file.polar").unwrap();
81 test.load_file(file!(), "test_file.polar").unwrap();
82 assert_eq!(
83 test.query("f(x)"),
84 vec![
85 hashmap! { "x" => 1, },
86 hashmap! { "x" => 2, },
87 hashmap! { "x" => 3, },
88 ]
89 );
90 assert_eq!(test.qvar::<u32>("f(x)", "x"), [1, 2, 3]);
91
92 test.oso.clear_rules();
93 test.load_file(file!(), "test_file.polar").unwrap();
94 test.load_file(file!(), "test_file_gx.polar").unwrap();
95 assert_eq!(
96 test.query("f(x)"),
97 vec![
98 hashmap! { "x" => 1, },
99 hashmap! { "x" => 2, },
100 hashmap! { "x" => 3, },
101 ]
102 );
103 assert_eq!(
104 test.query("g(x)"),
105 vec![
106 hashmap! { "x" => 1, },
107 hashmap! { "x" => 2, },
108 hashmap! { "x" => 3, },
109 ]
110 );
111 }
112
113 #[test]
test_type_mismatch_fails_unification()114 fn test_type_mismatch_fails_unification() {
115 common::setup();
116
117 #[derive(Eq, PartialEq, PolarClass, Clone, Default)]
118 struct Foo {}
119 #[derive(Eq, PartialEq, PolarClass, Clone, Default)]
120 struct Bar {}
121
122 let mut test = OsoTest::new();
123 test.oso
124 .register_class(
125 ClassBuilder::<Foo>::with_default()
126 .with_equality_check()
127 .build(),
128 )
129 .unwrap();
130
131 test.oso
132 .register_class(
133 ClassBuilder::<Bar>::with_default()
134 .with_equality_check()
135 .build(),
136 )
137 .unwrap();
138
139 test.qnull("new Foo() = new Bar()");
140 test.qnull("new Foo() = nil");
141 let rs = test.query("not new Foo() = nil");
142 assert_eq!(rs.len(), 1, "expected one result");
143 assert!(rs[0].is_empty(), "expected empty result");
144 }
145
146 #[test]
test_external()147 fn test_external() {
148 common::setup();
149
150 struct Foo {
151 a: &'static str,
152 }
153
154 impl Foo {
155 fn new(a: Option<&'static str>) -> Self {
156 Self {
157 a: a.unwrap_or("a"),
158 }
159 }
160
161 #[allow(dead_code)]
162 fn b(&self) -> impl Iterator<Item = &'static str> + Clone {
163 vec!["b"].into_iter()
164 }
165
166 fn c() -> &'static str {
167 "c"
168 }
169
170 fn d<X>(&self, x: X) -> X {
171 x
172 }
173
174 fn e(&self) -> Vec<u32> {
175 vec![1, 2, 3]
176 }
177
178 #[allow(dead_code)]
179 fn f(&self) -> impl Iterator<Item = Vec<u32>> + Clone {
180 vec![vec![1, 2, 3], vec![4, 5, 6], vec![7]].into_iter()
181 }
182
183 fn g(&self) -> std::collections::HashMap<&'static str, &'static str> {
184 hashmap!("hello" => "world")
185 }
186
187 fn h(&self) -> bool {
188 true
189 }
190 }
191
192 fn capital_foo() -> Foo {
193 Foo::new(Some("A"))
194 }
195
196 let mut test = OsoTest::new();
197
198 let foo_class = oso::ClassBuilder::with_constructor(capital_foo)
199 .name("Foo")
200 .add_attribute_getter("a", |receiver: &Foo| receiver.a)
201 // .add_method("b", |receiver: &Foo| oso::host::PolarResultIter(receiver.b()))
202 .add_class_method("c", Foo::c)
203 .add_method::<_, _, u32>("d", Foo::d)
204 .add_method("e", Foo::e)
205 // .add_method("f", |receiver: &Foo| oso::host::PolarResultIter(receiver.f()))
206 .add_method("g", Foo::g)
207 .add_method("h", Foo::h)
208 .build();
209 test.oso.register_class(foo_class).unwrap();
210
211 test.qvar_one("new Foo().a = x", "x", "A".to_string());
212 test.query_err("new Foo().a() = x");
213
214 // test.query_err("new Foo().b = x");
215 // test.qvar_one("new Foo().b() = x", "x", vec!["b".to_string()]);
216
217 test.qvar_one("Foo.c() = x", "x", "c".to_string());
218 test.qvar_one("new Foo().d(1) = x", "x", 1);
219 test.query_err("new Foo().d(\"1\") = x");
220 test.qvar_one("new Foo() = f and f.a = x", "x", "A".to_string());
221 test.qvar_one("new Foo().e() = x", "x", vec![1, 2, 3]);
222 // test.qvar_one(
223 // "new Foo().f() = x",
224 // "x",
225 // vec![vec![1, 2, 3], vec![4, 5, 6], vec![7]],
226 // );
227 test.qvar_one("new Foo().g().hello = x", "x", "world".to_string());
228 test.qvar_one("new Foo().h() = x", "x", true);
229 }
230
231 #[test]
232 //#[allow(clippy::redundant-closure)]
test_methods()233 fn test_methods() {
234 use std::default::Default;
235
236 common::setup();
237
238 #[derive(PolarClass, Clone)]
239 struct Foo {
240 #[polar(attribute)]
241 a: String,
242 }
243
244 #[derive(PolarClass, Debug, Clone)]
245 struct Bar {
246 #[polar(attribute)]
247 b: String,
248 }
249
250 impl Default for Bar {
251 fn default() -> Self {
252 Self {
253 b: "default".to_owned(),
254 }
255 }
256 }
257
258 impl Bar {
259 pub fn bar(&self) -> Bar {
260 self.clone()
261 }
262
263 pub fn foo(&self) -> Foo {
264 Foo { a: self.b.clone() }
265 }
266 }
267 let mut test = OsoTest::new();
268 test.oso.register_class(Foo::get_polar_class()).unwrap();
269 #[allow(clippy::redundant_closure)]
270 // @TODO: Not sure how to get the default call to typecheck without the closure wrapper.
271 test.oso
272 .register_class(
273 Bar::get_polar_class_builder()
274 .set_constructor(|| Bar::default())
275 .add_method("foo", |bar: &Bar| bar.foo())
276 .add_method("bar", |bar: &Bar| bar.bar())
277 .add_method("clone", Clone::clone)
278 .build(),
279 )
280 .unwrap();
281
282 // Test chaining
283 test.qvar_one(r#"new Bar().bar().foo().a = x"#, "x", "default".to_string());
284 // Test trait method.
285 test.qvar_one(r#"new Bar().clone().b = x"#, "x", "default".to_string());
286 }
287
288 #[test]
test_macros()289 fn test_macros() {
290 common::setup();
291
292 #[derive(PolarClass)]
293 #[polar(class_name = "Bar")]
294 struct Foo {
295 #[polar(attribute)]
296 a: String,
297 #[polar(attribute)]
298 b: String,
299 }
300
301 impl Foo {
302 fn new(a: String) -> Self {
303 Self {
304 a,
305 b: "b".to_owned(),
306 }
307 }
308
309 fn goodbye() -> Self {
310 Self {
311 a: "goodbye".to_owned(),
312 b: "b".to_owned(),
313 }
314 }
315 }
316
317 let mut test = OsoTest::new();
318 test.oso
319 .register_class(
320 Foo::get_polar_class_builder()
321 .set_constructor(Foo::new)
322 .build(),
323 )
324 .unwrap();
325
326 test.query(r#"new Bar("hello") = x"#);
327 test.qvar_one(r#"new Bar("hello").a = x"#, "x", "hello".to_string());
328 test.qvar_one(r#"new Bar("hello").b = x"#, "x", "b".to_string());
329
330 let class_builder = Foo::get_polar_class_builder();
331 let class = class_builder
332 .name("Baz")
333 .set_constructor(Foo::goodbye)
334 .add_method("world", |receiver: &Foo| format!("{} world", receiver.a))
335 .build();
336 test.oso.register_class(class).unwrap();
337
338 test.qvar_one(r#"new Baz().world() = x"#, "x", "goodbye world".to_string());
339 }
340
341 #[test]
test_tuple_structs()342 fn test_tuple_structs() {
343 common::setup();
344
345 #[derive(PolarClass)]
346 struct Foo(i32, i32);
347
348 impl Foo {
349 fn new(a: i32, b: i32) -> Self {
350 Self(a, b)
351 }
352 }
353
354 // @TODO: In the future when we can reason about which attributes are accessible types
355 // we can auto generate these accessors too. For now we have to rely on the attribute for
356 // fields and manually doing it for tuple structs.
357 // Also foo.0 isn't valid polar syntax so if we wanted something like that to work in general for "tuple like objects
358 // that requires a bigger change.
359 let mut test = OsoTest::new();
360 test.oso
361 .register_class(
362 Foo::get_polar_class_builder()
363 .set_constructor(Foo::new)
364 .add_attribute_getter("i0", |rcv: &Foo| rcv.0)
365 .add_attribute_getter("i1", |rcv: &Foo| rcv.1)
366 .build(),
367 )
368 .unwrap();
369
370 test.qvar_one(r#"foo = new Foo(1,2) and foo.i0 + foo.i1 = x"#, "x", 3);
371 }
372
373 #[test]
test_enums()374 fn test_enums() {
375 common::setup();
376
377 let mut test = OsoTest::new();
378
379 // test an enum with no variants
380 // this should simply not panic
381 #[derive(Clone, PolarClass)]
382 enum Foo {}
383
384 test.oso.register_class(Foo::get_polar_class()).unwrap();
385
386 // test an enum with variants
387 #[derive(Clone, Debug, PartialEq, PolarClass)]
388 enum Role {
389 Admin,
390 Member,
391 }
392
393 test.load_str(
394 r#"
395 is_admin(Role::Admin);
396 is_member(Role::Member);"#,
397 );
398
399 test.oso
400 .register_class(
401 Role::get_polar_class_builder()
402 .with_equality_check()
403 .build(),
404 )
405 .unwrap();
406
407 test.qvar_one(r#"is_admin(x)"#, "x", Role::Admin);
408 test.qvar_one(r#"is_member(x)"#, "x", Role::Member);
409 }
410
411 #[test]
test_enums_and_structs()412 fn test_enums_and_structs() {
413 common::setup();
414
415 let mut test = OsoTest::new();
416 test.load_str("allow(user: User, _action, _resource) if user.role = Role::Admin;");
417
418 #[derive(Clone, Debug, PolarClass)]
419 struct User {
420 name: String,
421 #[polar(attribute)]
422 role: Role,
423 }
424
425 #[derive(Clone, Debug, PartialEq, PolarClass)]
426 enum Role {
427 Admin,
428 Member,
429 }
430
431 test.oso.register_class(User::get_polar_class()).unwrap();
432
433 test.oso
434 .register_class(
435 Role::get_polar_class_builder()
436 .with_equality_check()
437 .build(),
438 )
439 .unwrap();
440
441 let admin = User {
442 name: "sudo".to_string(),
443 role: Role::Admin,
444 };
445
446 let member = User {
447 name: "not sudo".to_string(),
448 role: Role::Member,
449 };
450
451 assert!(test.oso.is_allowed(admin, "read", "resource").unwrap());
452 assert!(!test.oso.is_allowed(member, "read", "resource").unwrap());
453 }
454
455 #[test]
test_results_and_options()456 fn test_results_and_options() {
457 common::setup();
458
459 #[derive(PolarClass)]
460 struct Foo;
461
462 #[derive(Error, Debug)]
463 #[error("Test error")]
464 struct Error;
465
466 impl Foo {
467 fn new() -> Self {
468 Self
469 }
470 #[allow(clippy::unnecessary_wraps)]
471 fn ok(&self) -> Result<i32, Error> {
472 Ok(1)
473 }
474 #[allow(clippy::unnecessary_wraps)]
475 fn err(&self) -> Result<i32, Error> {
476 Err(Error)
477 }
478 #[allow(clippy::unnecessary_wraps)]
479 fn some(&self) -> Option<i32> {
480 Some(1)
481 }
482 #[allow(clippy::unnecessary_wraps)]
483 fn none(&self) -> Option<i32> {
484 None
485 }
486 }
487
488 let mut test = OsoTest::new();
489 test.oso
490 .register_class(
491 Foo::get_polar_class_builder()
492 .set_constructor(Foo::new)
493 .add_method("ok", Foo::ok)
494 .add_method("err", Foo::err)
495 .add_method("some", Foo::some)
496 .add_method("none", Foo::none)
497 .build(),
498 )
499 .unwrap();
500
501 test.qvar_one(r#"new Foo().ok() = x"#, "x", 1);
502 // TODO (dhatch): Assert type of error
503 // TODO (dhatch): Check nested method error
504 test.query_err("new Foo().err()");
505 test.qvar_one(r#"new Foo().some() = x"#, "x", Some(1));
506 test.qvar_one(r#"x in new Foo().some()"#, "x", 1);
507
508 // test.qnull(r#"new Foo().none() and y = 1"#);
509 test.qvar_one(r#"new Foo().none() = nil and y = 1"#, "y", 1);
510
511 let results = test.query("x in new Foo().none()");
512 assert!(results.is_empty());
513 }
514
515 // TODO: dhatch see if there is a relevant test to port.
516 #[test]
test_unify_externals()517 fn test_unify_externals() {
518 let mut test = OsoTest::new();
519
520 #[derive(PartialEq, Clone, Debug)]
521 struct Foo {
522 x: i64,
523 }
524
525 impl PolarClass for Foo {}
526 impl Foo {
527 fn new(x: i64) -> Self {
528 Self { x }
529 }
530 }
531
532 let foo_class = ClassBuilder::with_constructor(Foo::new)
533 .name("Foo")
534 .add_attribute_getter("x", |this: &Foo| this.x)
535 .with_equality_check()
536 .build();
537
538 test.oso.register_class(foo_class).unwrap();
539
540 test.load_str("foos_equal(a, b) if a = b;");
541
542 // Test with instantiated in polar.
543 test.qeval("foos_equal(new Foo(1), new Foo(1))");
544 test.qnull("foos_equal(new Foo(1), new Foo(2))");
545
546 let a = Foo::new(1);
547 let b = Foo::new(1);
548 assert_eq!(a, b);
549
550 let mut results = test.oso.query_rule("foos_equal", (a, b)).unwrap();
551 results.next().expect("At least one result").unwrap();
552
553 // Ensure that equality on a type that doesn't support it fails.
554 struct Bar {
555 x: i64,
556 }
557
558 impl PolarClass for Bar {}
559 impl Bar {
560 fn new(x: i64) -> Self {
561 Self { x }
562 }
563 }
564
565 let bar_class = ClassBuilder::with_constructor(Bar::new)
566 .name("Bar")
567 .add_attribute_getter("x", |this: &Bar| this.x)
568 .build();
569
570 test.oso.register_class(bar_class).unwrap();
571
572 #[derive(PartialEq, Clone, Debug)]
573 struct Baz {
574 x: i64,
575 }
576
577 impl PolarClass for Baz {}
578 impl Baz {
579 fn new(x: i64) -> Self {
580 Self { x }
581 }
582 }
583
584 let baz_class = ClassBuilder::with_constructor(Baz::new)
585 .name("Baz")
586 .add_attribute_getter("x", |this: &Baz| this.x)
587 .with_equality_check()
588 .build();
589
590 test.oso.register_class(baz_class).unwrap();
591 }
592
593 #[test]
test_values()594 fn test_values() {
595 let _ = tracing_subscriber::fmt::try_init();
596
597 #[derive(PolarClass)]
598 struct Foo;
599
600 impl Foo {
601 fn new() -> Self {
602 Self
603 }
604
605 fn one_two_three(&self) -> Vec<i32> {
606 vec![1, 2, 3]
607 }
608 }
609
610 let mut test = OsoTest::new();
611 test.oso
612 .register_class(
613 Foo::get_polar_class_builder()
614 .set_constructor(Foo::new)
615 .add_iterator_method("one_two_three", Foo::one_two_three)
616 .add_method("as_list", Foo::one_two_three)
617 .build(),
618 )
619 .unwrap();
620
621 let results: Vec<i32> = test.qvar("x in new Foo().one_two_three()", "x");
622 assert!(results == vec![1, 2, 3]);
623 let result: Vec<Vec<i32>> = test.qvar("new Foo().as_list() = x", "x");
624 assert!(result == vec![vec![1, 2, 3]]);
625 }
626
627 #[test]
test_arg_number()628 fn test_arg_number() {
629 let _ = tracing_subscriber::fmt::try_init();
630 #[derive(PolarClass)]
631 struct Foo;
632
633 impl Foo {
634 fn three(&self, one: i32, two: i32, three: i32) -> i32 {
635 one + two + three
636 }
637
638 fn many_method(
639 &self,
640 one: i32,
641 two: i32,
642 three: i32,
643 four: i32,
644 five: i32,
645 six: i32,
646 seven: i32,
647 ) -> i32 {
648 one + two + three + four + five + six + seven
649 }
650
651 fn many_class_method(
652 one: i32,
653 two: i32,
654 three: i32,
655 four: i32,
656 five: i32,
657 six: i32,
658 seven: i32,
659 ) -> i32 {
660 one + two + three + four + five + six + seven
661 }
662 }
663
664 let mut test = OsoTest::new();
665 test.oso
666 .register_class(
667 Foo::get_polar_class_builder()
668 .add_method("many_method", Foo::three)
669 .add_method("many_method", Foo::many_method)
670 .add_class_method("many_class", Foo::many_class_method)
671 .build(),
672 )
673 .unwrap();
674 }
675
676 #[test]
test_without_registering()677 fn test_without_registering() {
678 let _ = tracing_subscriber::fmt::try_init();
679 #[derive(Clone, PolarClass)]
680 struct Foo {
681 #[polar(attribute)]
682 x: u32,
683 }
684
685 let test = OsoTest::new();
686 test.oso.load_str("f(foo: Foo) if 1 = foo.x;").unwrap();
687 test.oso
688 .query_rule("f", (Foo { x: 1 },))
689 .unwrap()
690 .next()
691 .unwrap()
692 .unwrap();
693 }
694
695 #[test]
test_option()696 fn test_option() {
697 let _ = tracing_subscriber::fmt::try_init();
698 #[derive(Clone, Default, PolarClass)]
699 struct Foo;
700
701 impl Foo {
702 #[allow(clippy::unnecessary_wraps)]
703 fn get_some(&self) -> Option<i32> {
704 Some(12)
705 }
706 #[allow(clippy::unnecessary_wraps)]
707 fn get_none(&self) -> Option<i32> {
708 None
709 }
710 }
711
712 let mut test = OsoTest::new();
713 test.oso
714 .register_class(
715 Foo::get_polar_class_builder()
716 .set_constructor(Foo::default)
717 .add_method("get_some", Foo::get_some)
718 .add_method("get_none", Foo::get_none)
719 .build(),
720 )
721 .unwrap();
722 test.qvar_one("new Foo().get_some() = x", "x", Some(12i32));
723 test.qvar_one("x in new Foo().get_some()", "x", 12i32);
724 test.qvar_one("new Foo().get_none() = x", "x", Option::<i32>::None);
725 test.qeval("12 in new Foo().get_some()");
726 test.qeval("new Foo().get_none() = nil");
727 }
728
729 #[cfg(feature = "uuid-06")]
730 #[test]
test_uuid_06() -> Result<(), Box<dyn std::error::Error>>731 fn test_uuid_06() -> Result<(), Box<dyn std::error::Error>> {
732 use uuid_06::Uuid;
733 let mut test = OsoTest::new();
734 test.oso.register_class(Uuid::get_polar_class())?;
735 test.load_str("f(x: Uuid, y: Uuid) if x = y;");
736 let (x, y) = (Uuid::nil(), Uuid::nil());
737 test.oso.query_rule("f", (x, y))?.next().unwrap()?;
738 Ok(())
739 }
740
741 #[cfg(feature = "uuid-07")]
742 #[test]
test_uuid_07() -> Result<(), Box<dyn std::error::Error>>743 fn test_uuid_07() -> Result<(), Box<dyn std::error::Error>> {
744 use uuid_07::Uuid;
745 let mut test = OsoTest::new();
746 test.oso.register_class(Uuid::get_polar_class())?;
747 test.load_str("f(x: Uuid, y: Uuid) if x = y;");
748 let (x, y) = (Uuid::nil(), Uuid::nil());
749 test.oso.query_rule("f", (x, y))?.next().unwrap()?;
750 Ok(())
751 }
752