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