├── .gitignore ├── Cargo.toml ├── Cross.toml ├── README.md ├── docker └── Dockerfile.armv7-unknown-linux-gnueabihf ├── opa-bench ├── .gitignore ├── Cargo.toml ├── benches │ ├── activity.rego │ ├── activity.rs │ ├── simple.rego │ └── simple_eval.rs ├── examples │ ├── activity.rego │ └── activity.rs └── src │ └── lib.rs ├── opa-go-sys ├── .gitignore ├── Cargo.toml ├── build.rs ├── go.mod ├── go.sum ├── opa.go ├── opa_test.go └── src │ └── lib.rs ├── opa-go ├── Cargo.toml ├── src │ ├── lib.rs │ └── wasm.rs └── tests │ ├── compile.rs │ ├── empty.rego │ └── empty.wasm ├── opa-rego ├── Cargo.toml └── src │ └── lib.rs └── opa-wasm ├── Cargo.toml ├── examples ├── eval.rs ├── example.rego └── input.json ├── proptest-regressions └── opa │ └── mod.txt ├── src ├── builtins │ ├── aggregates.rs │ ├── arrays.rs │ ├── mod.rs │ ├── net.rs │ ├── numbers.rs │ ├── objects.rs │ ├── regex.rs │ ├── sets.rs │ ├── strings.rs │ ├── time.rs │ └── types.rs ├── error.rs ├── lib.rs ├── opa_serde │ ├── de.rs │ ├── error.rs │ ├── mod.rs │ └── ser.rs ├── runtime │ ├── mod.rs │ ├── wasmi.rs │ └── wasmtime.rs ├── set.rs └── value │ ├── de.rs │ ├── from.rs │ ├── index.rs │ ├── mod.rs │ ├── number.rs │ └── ser.rs └── tests ├── empty.rego ├── empty.wasm ├── eval_struct_test.rego ├── eval_struct_test.rs ├── types.rego └── types.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "opa-bench", 4 | "opa-go", 5 | "opa-go-sys", 6 | "opa-rego", 7 | "opa-wasm", 8 | ] 9 | 10 | [profile.release] 11 | debug = true 12 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.armv7-unknown-linux-gnueabihf] 2 | image = "myagley/cross-go:armv7-unknown-linux-gnueabihf-0.2.0" 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Policy Agent 2 | The [Open Policy Agent](https://www.openpolicyagent.org/docs/latest/) (OPA, pronounced “oh-pa”) is an open source, general-purpose policy engine that unifies policy enforcement across the stack. 3 | OPA provides a high-level declarative language that let’s you specify policy as code and simple APIs to offload policy decision-making from your software. 4 | You can use OPA to enforce policies in microservices, Kubernetes, CI/CD pipelines, API gateways, and more. 5 | 6 | -------- 7 | 8 | This project is a rust integration for OPA. 9 | Its primary integration point is via [Web Assembly](https://www.openpolicyagent.org/docs/latest/wasm/). 10 | 11 | # Quickstart 12 | 13 | This assumes you have the OPA [cli tool](https://www.openpolicyagent.org/docs/latest/#running-opa) on the path to compile Rego files to WASM. 14 | 15 | To run the example: 16 | 17 | ```sh 18 | $ RUST_LOG=debug cargo run --example eval -- -q 'data.example.allow' -p opa-wasm/examples/example.rego -i opa-wasm/examples/input.json 19 | ``` 20 | 21 | You should see the following output, indicating that `data.example.allow` query is defined for the `opa-wasm/examples/input.json` input. 22 | 23 | ``` 24 | result: {{}} 25 | ``` 26 | 27 | The result is a set of variable bindings where the query is defined. 28 | In this case, the result is a set of size one, meaning that the query is defined for this input. 29 | -------------------------------------------------------------------------------- /docker/Dockerfile.armv7-unknown-linux-gnueabihf: -------------------------------------------------------------------------------- 1 | FROM rustembedded/cross:armv7-unknown-linux-gnueabihf-0.2.0 2 | 3 | ENV GOLANG_VERSION 1.14.2 4 | 5 | # gcc for cgo 6 | RUN apt-get update && apt-get install -y --no-install-recommends \ 7 | g++ \ 8 | gcc \ 9 | clang \ 10 | libc6-dev \ 11 | make \ 12 | pkg-config \ 13 | wget \ 14 | && rm -rf /var/lib/apt/lists/* 15 | 16 | RUN set -eux; \ 17 | \ 18 | # this "case" statement is generated via "update.sh" 19 | dpkgArch="$(dpkg --print-architecture)"; \ 20 | case "${dpkgArch##*-}" in \ 21 | amd64) goRelArch='linux-amd64'; goRelSha256='6272d6e940ecb71ea5636ddb5fab3933e087c1356173c61f4a803895e947ebb3' ;; \ 22 | armhf) goRelArch='linux-armv6l'; goRelSha256='eb4550ba741506c2a4057ea4d3a5ad7ed5a887de67c7232f1e4795464361c83c' ;; \ 23 | arm64) goRelArch='linux-arm64'; goRelSha256='bb6d22fe5806352c3d0826676654e09b6e41eb1af52e8d506d3fa85adf7f8d88' ;; \ 24 | i386) goRelArch='linux-386'; goRelSha256='cab5f51e6ffb616c6ee963c3d0650ca4e3c4108307c44f2baf233fcb8ff098f6' ;; \ 25 | ppc64el) goRelArch='linux-ppc64le'; goRelSha256='48c22268c81ced9084a43bbe2c1596d3e636b5560b30a32434a7f15e561de160' ;; \ 26 | s390x) goRelArch='linux-s390x'; goRelSha256='501cc919648c9d85b901963303c5061ea6814c80f0d35fda9e62980d3ff58cf4' ;; \ 27 | *) goRelArch='src'; goRelSha256='98de84e69726a66da7b4e58eac41b99cbe274d7e8906eeb8a5b7eb0aadee7f7c'; \ 28 | echo >&2; echo >&2 "warning: current architecture ($dpkgArch) does not have a corresponding Go binary release; will be building from source"; echo >&2 ;; \ 29 | esac; \ 30 | \ 31 | url="https://golang.org/dl/go${GOLANG_VERSION}.${goRelArch}.tar.gz"; \ 32 | wget -O go.tgz "$url"; \ 33 | echo "${goRelSha256} *go.tgz" | sha256sum -c -; \ 34 | tar -C /usr/local -xzf go.tgz; \ 35 | rm go.tgz; \ 36 | \ 37 | if [ "$goRelArch" = 'src' ]; then \ 38 | echo >&2; \ 39 | echo >&2 'error: UNIMPLEMENTED'; \ 40 | echo >&2 'TODO install golang-any from jessie-backports for GOROOT_BOOTSTRAP (and uninstall after build)'; \ 41 | echo >&2; \ 42 | exit 1; \ 43 | fi; \ 44 | \ 45 | export PATH="/usr/local/go/bin:$PATH"; \ 46 | go version 47 | 48 | ENV GOPATH /go 49 | ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH 50 | ENV XDG_CACHE_HOME /tmp/.cache 51 | 52 | RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH" 53 | WORKDIR $GOPATH 54 | -------------------------------------------------------------------------------- /opa-bench/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /opa-bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "opa-bench" 3 | version = "0.1.0" 4 | authors = ["Mike Yagley "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [lib] 9 | bench = false 10 | 11 | [dev-dependencies] 12 | criterion = "0.3" 13 | serde = "1" 14 | 15 | opa-go = { path = "../opa-go" } 16 | opa-rego = { path = "../opa-rego" } 17 | opa-wasm = { path = "../opa-wasm" } 18 | rego = { path = "../../rego" } 19 | 20 | [[bench]] 21 | name = "simple_eval" 22 | harness = false 23 | 24 | [[bench]] 25 | name = "activity" 26 | harness = false 27 | -------------------------------------------------------------------------------- /opa-bench/benches/activity.rego: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | allow { 4 | input.operation.connect 5 | input.auth_id.identity == "auth_id" 6 | input.client_id == "client_id" 7 | } 8 | -------------------------------------------------------------------------------- /opa-bench/benches/activity.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::net::{IpAddr, Ipv4Addr}; 3 | use std::path::PathBuf; 4 | 5 | use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; 6 | use rego::{Index, Map, ToValue, Value}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 10 | pub struct ClientId(String); 11 | 12 | impl From for ClientId { 13 | fn from(id: String) -> Self { 14 | Self(id) 15 | } 16 | } 17 | 18 | impl Index for ClientId { 19 | fn index(&self, _field: &Value<'_>) -> Option> { 20 | None 21 | } 22 | } 23 | 24 | impl ToValue for ClientId { 25 | fn to_value(&self) -> Value<'_> { 26 | Value::String(Cow::Borrowed(self.0.as_str())) 27 | } 28 | } 29 | 30 | /// Authenticated MQTT client identity. 31 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 32 | #[serde(rename_all = "snake_case")] 33 | pub enum AuthId { 34 | /// Identity for anonymous client. 35 | Anonymous, 36 | 37 | /// Identity for non-anonymous client. 38 | Identity(Identity), 39 | } 40 | 41 | impl std::fmt::Display for AuthId { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | match self { 44 | Self::Anonymous => write!(f, "*"), 45 | Self::Identity(identity) => write!(f, "{}", identity), 46 | } 47 | } 48 | } 49 | 50 | impl AuthId { 51 | /// Creates a MQTT identity for known client. 52 | pub fn from_identity>(identity: T) -> Self { 53 | Self::Identity(identity.into()) 54 | } 55 | } 56 | 57 | impl> From for AuthId { 58 | fn from(identity: T) -> Self { 59 | AuthId::from_identity(identity) 60 | } 61 | } 62 | 63 | impl Index for AuthId { 64 | fn index(&self, field: &Value<'_>) -> Option> { 65 | if let Value::String(field) = field { 66 | match field.as_ref() { 67 | "identity" => { 68 | if let Self::Identity(id) = self { 69 | Some(Value::Ref(id)) 70 | } else { 71 | None 72 | } 73 | } 74 | "anonymous" => { 75 | if let Self::Anonymous = self { 76 | Some(Value::Null) 77 | } else { 78 | None 79 | } 80 | } 81 | _ => None, 82 | } 83 | } else { 84 | None 85 | } 86 | } 87 | } 88 | 89 | impl ToValue for AuthId { 90 | fn to_value(&self) -> Value<'_> { 91 | let mut obj = Map::new(); 92 | match self { 93 | Self::Anonymous => obj.insert(Value::from("anonymous"), Value::Null), 94 | Self::Identity(i) => obj.insert(Value::from("identity"), i.to_value()), 95 | }; 96 | Value::Object(obj) 97 | } 98 | } 99 | 100 | /// Non-anonymous client identity. 101 | pub type Identity = String; 102 | 103 | /// Describes a client activity to authorized. 104 | #[derive(Serialize, Deserialize, Debug)] 105 | #[serde(rename_all = "snake_case")] 106 | pub struct Activity { 107 | auth_id: AuthId, 108 | client_id: ClientId, 109 | operation: Operation, 110 | } 111 | 112 | impl Activity { 113 | pub fn new( 114 | auth_id: impl Into, 115 | client_id: impl Into, 116 | operation: Operation, 117 | ) -> Self { 118 | Self { 119 | auth_id: auth_id.into(), 120 | client_id: client_id.into(), 121 | operation, 122 | } 123 | } 124 | 125 | pub fn client_id(&self) -> &ClientId { 126 | &self.client_id 127 | } 128 | 129 | pub fn auth_id(&self) -> &AuthId { 130 | &self.auth_id 131 | } 132 | 133 | pub fn operation(&self) -> &Operation { 134 | &self.operation 135 | } 136 | } 137 | 138 | impl Index for Activity { 139 | fn index(&self, field: &Value<'_>) -> Option> { 140 | if let Value::String(field) = field { 141 | match field.as_ref() { 142 | "auth_id" => Some(Value::Ref(&self.auth_id)), 143 | "client_id" => Some(Value::Ref(&self.client_id)), 144 | "operation" => Some(Value::Ref(&self.operation)), 145 | _ => None, 146 | } 147 | } else { 148 | None 149 | } 150 | } 151 | } 152 | 153 | impl ToValue for Activity { 154 | fn to_value(&self) -> Value<'_> { 155 | let mut obj = Map::new(); 156 | obj.insert(Value::from("auth_id"), self.auth_id.to_value()); 157 | obj.insert(Value::from("client_id"), self.client_id.to_value()); 158 | obj.insert(Value::from("operation"), self.operation.to_value()); 159 | Value::Object(obj) 160 | } 161 | } 162 | 163 | /// Describes a client operation to be authorized. 164 | #[derive(Serialize, Deserialize, Debug)] 165 | #[serde(rename_all = "snake_case")] 166 | pub enum Operation { 167 | Connect(Connect), 168 | Publish(Publish), 169 | Subscribe(Subscribe), 170 | Receive(Receive), 171 | } 172 | 173 | impl Operation { 174 | /// Creates a new operation context for CONNECT request. 175 | pub fn new_connect() -> Self { 176 | let c = Connect { 177 | remote_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 178 | will: None, 179 | }; 180 | Self::Connect(c) 181 | } 182 | 183 | // /// Creates a new operation context for PUBLISH request. 184 | // pub fn new_publish(publish: proto::Publish) -> Self { 185 | // Self::Publish(publish.into()) 186 | // } 187 | // 188 | // /// Creates a new operation context for SUBSCRIBE request. 189 | // pub fn new_subscribe(subscribe_to: proto::SubscribeTo) -> Self { 190 | // Self::Subscribe(subscribe_to.into()) 191 | // } 192 | // 193 | // /// Creates a new operation context for RECEIVE request. 194 | // /// 195 | // /// RECEIVE request happens when broker decides to publish a message to a certain 196 | // /// topic client subscribed to. 197 | // pub fn new_receive(publication: proto::Publication) -> Self { 198 | // Self::Receive(publication.into()) 199 | // } 200 | } 201 | 202 | impl Index for Operation { 203 | fn index(&self, field: &Value<'_>) -> Option> { 204 | if let Value::String(field) = field { 205 | match field.as_ref() { 206 | "connect" => { 207 | if let Self::Connect(connect) = self { 208 | Some(Value::Ref(connect)) 209 | } else { 210 | None 211 | } 212 | } 213 | "publish" => { 214 | if let Self::Publish(publish) = self { 215 | Some(Value::Ref(publish)) 216 | } else { 217 | None 218 | } 219 | } 220 | "subscribe" => { 221 | if let Self::Subscribe(subscribe) = self { 222 | Some(Value::Ref(subscribe)) 223 | } else { 224 | None 225 | } 226 | } 227 | "receive" => { 228 | if let Self::Receive(receive) = self { 229 | Some(Value::Ref(receive)) 230 | } else { 231 | None 232 | } 233 | } 234 | _ => None, 235 | } 236 | } else { 237 | None 238 | } 239 | } 240 | } 241 | 242 | impl ToValue for Operation { 243 | fn to_value(&self) -> Value<'_> { 244 | let mut obj = Map::new(); 245 | match self { 246 | Self::Connect(c) => obj.insert(Value::from("connect"), c.to_value()), 247 | Self::Publish(p) => obj.insert(Value::from("publish"), p.to_value()), 248 | Self::Subscribe(s) => obj.insert(Value::from("subscribe"), s.to_value()), 249 | Self::Receive(r) => obj.insert(Value::from("receive"), r.to_value()), 250 | }; 251 | Value::Object(obj) 252 | } 253 | } 254 | 255 | /// Represents a client attempt to connect to the broker. 256 | #[derive(Serialize, Deserialize, Debug)] 257 | #[serde(rename_all = "snake_case")] 258 | pub struct Connect { 259 | remote_addr: IpAddr, 260 | will: Option, 261 | } 262 | 263 | impl Index for Connect { 264 | fn index(&self, field: &Value<'_>) -> Option> { 265 | if let Value::String(s) = field { 266 | match s.as_ref() { 267 | "remote_addr" => Some(Value::String(Cow::Owned(self.remote_addr.to_string()))), 268 | "will" => Some(Value::Ref(&self.will)), 269 | _ => None, 270 | } 271 | } else { 272 | None 273 | } 274 | } 275 | } 276 | 277 | impl ToValue for Connect { 278 | fn to_value(&self) -> Value<'_> { 279 | let mut obj = Map::new(); 280 | obj.insert( 281 | Value::String(Cow::Borrowed("remote_addr")), 282 | Value::String(Cow::Owned(self.remote_addr.to_string())), 283 | ); 284 | obj.insert(Value::String(Cow::Borrowed("will")), self.will.to_value()); 285 | Value::Object(obj) 286 | } 287 | } 288 | 289 | /// Represents a publication description without payload to be used for authorization. 290 | #[derive(Serialize, Deserialize, Debug)] 291 | #[serde(rename_all = "snake_case")] 292 | pub struct Publication { 293 | // topic_name: String, 294 | // qos: proto::QoS, 295 | // retain: bool, 296 | } 297 | 298 | // impl Publication { 299 | // pub fn topic_name(&self) -> &str { 300 | // &self.topic_name 301 | // } 302 | // } 303 | // 304 | // impl From for Publication { 305 | // fn from(publication: proto::Publication) -> Self { 306 | // Self { 307 | // topic_name: publication.topic_name, 308 | // qos: publication.qos, 309 | // retain: publication.retain, 310 | // } 311 | // } 312 | // } 313 | 314 | impl Index for Publication { 315 | fn index(&self, _field: &Value<'_>) -> Option> { 316 | None 317 | } 318 | } 319 | 320 | impl ToValue for Publication { 321 | fn to_value(&self) -> Value<'_> { 322 | Value::Null 323 | } 324 | } 325 | 326 | /// Represents a client attempt to publish a new message on a specified MQTT topic. 327 | #[derive(Serialize, Deserialize, Debug)] 328 | #[serde(rename_all = "snake_case")] 329 | pub struct Publish { 330 | // publication: Publication, 331 | } 332 | 333 | // impl Publish { 334 | // pub fn publication(&self) -> &Publication { 335 | // &self.publication 336 | // } 337 | // } 338 | // 339 | // impl From for Publish { 340 | // fn from(publish: proto::Publish) -> Self { 341 | // Self { 342 | // publication: Publication { 343 | // topic_name: publish.topic_name, 344 | // qos: match publish.packet_identifier_dup_qos { 345 | // proto::PacketIdentifierDupQoS::AtMostOnce => proto::QoS::AtMostOnce, 346 | // proto::PacketIdentifierDupQoS::AtLeastOnce(_, _) => proto::QoS::AtLeastOnce, 347 | // proto::PacketIdentifierDupQoS::ExactlyOnce(_, _) => proto::QoS::ExactlyOnce, 348 | // }, 349 | // retain: publish.retain, 350 | // }, 351 | // } 352 | // } 353 | // } 354 | 355 | impl Index for Publish { 356 | fn index(&self, _field: &Value<'_>) -> Option> { 357 | None 358 | } 359 | } 360 | 361 | impl ToValue for Publish { 362 | fn to_value(&self) -> Value<'_> { 363 | Value::Null 364 | } 365 | } 366 | 367 | /// Represents a client attempt to subscribe to a specified MQTT topic in order to received messages. 368 | #[derive(Serialize, Deserialize, Debug)] 369 | #[serde(rename_all = "snake_case")] 370 | pub struct Subscribe { 371 | // topic_filter: String, 372 | // qos: proto::QoS, 373 | } 374 | 375 | // impl Subscribe { 376 | // pub fn topic_filter(&self) -> &str { 377 | // &self.topic_filter 378 | // } 379 | // } 380 | // 381 | // impl From for Subscribe { 382 | // fn from(subscribe_to: proto::SubscribeTo) -> Self { 383 | // Self { 384 | // topic_filter: subscribe_to.topic_filter, 385 | // qos: subscribe_to.qos, 386 | // } 387 | // } 388 | // } 389 | 390 | impl Index for Subscribe { 391 | fn index(&self, _field: &Value<'_>) -> Option> { 392 | None 393 | } 394 | } 395 | 396 | impl ToValue for Subscribe { 397 | fn to_value(&self) -> Value<'_> { 398 | Value::Null 399 | } 400 | } 401 | 402 | /// Represents a client to received a message from a specified MQTT topic. 403 | #[derive(Serialize, Deserialize, Debug)] 404 | #[serde(rename_all = "snake_case")] 405 | pub struct Receive { 406 | // publication: Publication, 407 | } 408 | 409 | // impl From for Receive { 410 | // fn from(publication: proto::Publication) -> Self { 411 | // Self { 412 | // publication: publication.into(), 413 | // } 414 | // } 415 | // } 416 | 417 | impl Index for Receive { 418 | fn index(&self, _field: &Value<'_>) -> Option> { 419 | None 420 | } 421 | } 422 | 423 | impl ToValue for Receive { 424 | fn to_value(&self) -> Value<'_> { 425 | Value::Null 426 | } 427 | } 428 | 429 | pub fn bench_activity(c: &mut Criterion) { 430 | let query = "data.test.allow"; 431 | let mut module_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 432 | module_path.push("benches/activity.rego"); 433 | let module = std::fs::read_to_string(&module_path).unwrap(); 434 | let wasm = opa_go::wasm::compile("data.test.allow", &module_path).unwrap(); 435 | 436 | let go = opa_go::Rego::new(query, "test", module.as_str()).unwrap(); 437 | let mut wasm = opa_wasm::Policy::from_wasm(&wasm).unwrap(); 438 | let mut rego = opa_rego::Policy::from_query(query, &[module.as_str()]).unwrap(); 439 | 440 | let mut group = c.benchmark_group("activity"); 441 | 442 | group.bench_function(BenchmarkId::new("go", "connect"), |b| { 443 | b.iter(|| { 444 | let operation = Operation::new_connect(); 445 | let activity = Activity::new( 446 | "auth_id".to_string(), 447 | ClientId("client_id".to_string()), 448 | operation, 449 | ); 450 | 451 | let result = go.eval_bool(black_box(&activity)).unwrap(); 452 | assert_eq!(true, result); 453 | }) 454 | }); 455 | 456 | group.bench_function(BenchmarkId::new("wasm", "connect"), |b| { 457 | b.iter(|| { 458 | let operation = Operation::new_connect(); 459 | let activity = Activity::new( 460 | "auth_id".to_string(), 461 | ClientId("client_id".to_string()), 462 | operation, 463 | ); 464 | let result = wasm.evaluate(black_box(&activity)); 465 | assert!(result.is_ok()); 466 | }) 467 | }); 468 | 469 | group.bench_function(BenchmarkId::new("rust-rego", "connect"), |b| { 470 | b.iter(|| { 471 | let operation = Operation::new_connect(); 472 | let activity = Activity::new( 473 | "auth_id".to_string(), 474 | ClientId("client_id".to_string()), 475 | operation, 476 | ); 477 | let result: bool = rego.evaluate(black_box(activity)).unwrap(); 478 | assert_eq!(true, result); 479 | }) 480 | }); 481 | 482 | group.finish(); 483 | } 484 | 485 | criterion_group!(benches, bench_activity); 486 | criterion_main!(benches); 487 | -------------------------------------------------------------------------------- /opa-bench/benches/simple.rego: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | default allow = true 4 | -------------------------------------------------------------------------------- /opa-bench/benches/simple_eval.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; 4 | 5 | pub fn bench_simple_eval(c: &mut Criterion) { 6 | let query = "data.test.allow"; 7 | let mut module_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 8 | module_path.push("benches/simple.rego"); 9 | let module = std::fs::read_to_string(&module_path).unwrap(); 10 | let wasm = opa_go::wasm::compile("data.test.allow", &module_path).unwrap(); 11 | 12 | let go = opa_go::Rego::new(query, "test", module.as_str()).unwrap(); 13 | let mut wasm = opa_wasm::Policy::from_wasm(&wasm).unwrap(); 14 | let mut rego = opa_rego::Policy::from_query(query, &[module.as_str()]).unwrap(); 15 | 16 | let mut group = c.benchmark_group("simple eval"); 17 | 18 | group.bench_function(BenchmarkId::new("go", "default true"), |b| { 19 | b.iter(|| { 20 | let result = go.eval_bool(black_box(&())).unwrap(); 21 | assert_eq!(true, result); 22 | }) 23 | }); 24 | 25 | group.bench_function(BenchmarkId::new("wasm", "default true"), |b| { 26 | b.iter(|| { 27 | let result = wasm.evaluate(black_box(&())); 28 | assert!(result.is_ok()); 29 | }) 30 | }); 31 | 32 | group.bench_function(BenchmarkId::new("rust-rego", "default true"), |b| { 33 | b.iter(|| { 34 | let result: bool = rego.evaluate(black_box(())).unwrap(); 35 | assert_eq!(true, result); 36 | }) 37 | }); 38 | 39 | group.finish(); 40 | } 41 | 42 | criterion_group!(benches, bench_simple_eval); 43 | criterion_main!(benches); 44 | -------------------------------------------------------------------------------- /opa-bench/examples/activity.rego: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | allow { 4 | input.operation.connect 5 | input.auth_id.identity == "auth_id" 6 | input.client_id == "client_id" 7 | } 8 | -------------------------------------------------------------------------------- /opa-bench/examples/activity.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::net::{IpAddr, Ipv4Addr}; 3 | use std::path::PathBuf; 4 | 5 | use rego::{Index, Map, ToValue, Value}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 9 | pub struct ClientId(String); 10 | 11 | impl From for ClientId { 12 | fn from(id: String) -> Self { 13 | Self(id) 14 | } 15 | } 16 | 17 | impl Index for ClientId { 18 | fn index(&self, _field: &Value<'_>) -> Option> { 19 | None 20 | } 21 | } 22 | 23 | impl ToValue for ClientId { 24 | fn to_value(&self) -> Value<'_> { 25 | Value::String(Cow::Borrowed(self.0.as_str())) 26 | } 27 | } 28 | 29 | /// Authenticated MQTT client identity. 30 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 31 | #[serde(rename_all = "snake_case")] 32 | pub enum AuthId { 33 | /// Identity for anonymous client. 34 | Anonymous, 35 | 36 | /// Identity for non-anonymous client. 37 | Identity(Identity), 38 | } 39 | 40 | impl std::fmt::Display for AuthId { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | match self { 43 | Self::Anonymous => write!(f, "*"), 44 | Self::Identity(identity) => write!(f, "{}", identity), 45 | } 46 | } 47 | } 48 | 49 | impl AuthId { 50 | /// Creates a MQTT identity for known client. 51 | pub fn from_identity>(identity: T) -> Self { 52 | Self::Identity(identity.into()) 53 | } 54 | } 55 | 56 | impl> From for AuthId { 57 | fn from(identity: T) -> Self { 58 | AuthId::from_identity(identity) 59 | } 60 | } 61 | 62 | impl Index for AuthId { 63 | fn index(&self, field: &Value<'_>) -> Option> { 64 | if let Value::String(field) = field { 65 | match field.as_ref() { 66 | "identity" => { 67 | if let Self::Identity(id) = self { 68 | Some(Value::Ref(id)) 69 | } else { 70 | None 71 | } 72 | } 73 | "anonymous" => { 74 | if let Self::Anonymous = self { 75 | Some(Value::Null) 76 | } else { 77 | None 78 | } 79 | } 80 | _ => None, 81 | } 82 | } else { 83 | None 84 | } 85 | } 86 | } 87 | 88 | impl ToValue for AuthId { 89 | fn to_value(&self) -> Value<'_> { 90 | let mut obj = Map::new(); 91 | match self { 92 | Self::Anonymous => obj.insert(Value::from("anonymous"), Value::Null), 93 | Self::Identity(i) => obj.insert(Value::from("identity"), i.to_value()), 94 | }; 95 | Value::Object(obj) 96 | } 97 | } 98 | 99 | /// Non-anonymous client identity. 100 | pub type Identity = String; 101 | 102 | /// Describes a client activity to authorized. 103 | #[derive(Serialize, Deserialize, Debug)] 104 | #[serde(rename_all = "snake_case")] 105 | pub struct Activity { 106 | auth_id: AuthId, 107 | client_id: ClientId, 108 | operation: Operation, 109 | } 110 | 111 | impl Activity { 112 | pub fn new( 113 | auth_id: impl Into, 114 | client_id: impl Into, 115 | operation: Operation, 116 | ) -> Self { 117 | Self { 118 | auth_id: auth_id.into(), 119 | client_id: client_id.into(), 120 | operation, 121 | } 122 | } 123 | 124 | pub fn client_id(&self) -> &ClientId { 125 | &self.client_id 126 | } 127 | 128 | pub fn auth_id(&self) -> &AuthId { 129 | &self.auth_id 130 | } 131 | 132 | pub fn operation(&self) -> &Operation { 133 | &self.operation 134 | } 135 | } 136 | 137 | impl Index for Activity { 138 | fn index(&self, field: &Value<'_>) -> Option> { 139 | if let Value::String(field) = field { 140 | match field.as_ref() { 141 | "auth_id" => Some(Value::Ref(&self.auth_id)), 142 | "client_id" => Some(Value::Ref(&self.client_id)), 143 | "operation" => Some(Value::Ref(&self.operation)), 144 | _ => None, 145 | } 146 | } else { 147 | None 148 | } 149 | } 150 | } 151 | 152 | impl ToValue for Activity { 153 | fn to_value(&self) -> Value<'_> { 154 | let mut obj = Map::new(); 155 | obj.insert(Value::from("auth_id"), self.auth_id.to_value()); 156 | obj.insert(Value::from("client_id"), self.client_id.to_value()); 157 | obj.insert(Value::from("operation"), self.operation.to_value()); 158 | Value::Object(obj) 159 | } 160 | } 161 | 162 | /// Describes a client operation to be authorized. 163 | #[derive(Serialize, Deserialize, Debug)] 164 | #[serde(rename_all = "snake_case")] 165 | pub enum Operation { 166 | Connect(Connect), 167 | Publish(Publish), 168 | Subscribe(Subscribe), 169 | Receive(Receive), 170 | } 171 | 172 | impl Operation { 173 | /// Creates a new operation context for CONNECT request. 174 | pub fn new_connect() -> Self { 175 | let c = Connect { 176 | remote_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 177 | will: None, 178 | }; 179 | Self::Connect(c) 180 | } 181 | 182 | // /// Creates a new operation context for PUBLISH request. 183 | // pub fn new_publish(publish: proto::Publish) -> Self { 184 | // Self::Publish(publish.into()) 185 | // } 186 | // 187 | // /// Creates a new operation context for SUBSCRIBE request. 188 | // pub fn new_subscribe(subscribe_to: proto::SubscribeTo) -> Self { 189 | // Self::Subscribe(subscribe_to.into()) 190 | // } 191 | // 192 | // /// Creates a new operation context for RECEIVE request. 193 | // /// 194 | // /// RECEIVE request happens when broker decides to publish a message to a certain 195 | // /// topic client subscribed to. 196 | // pub fn new_receive(publication: proto::Publication) -> Self { 197 | // Self::Receive(publication.into()) 198 | // } 199 | } 200 | 201 | impl Index for Operation { 202 | fn index(&self, field: &Value<'_>) -> Option> { 203 | if let Value::String(field) = field { 204 | match field.as_ref() { 205 | "connect" => { 206 | if let Self::Connect(connect) = self { 207 | Some(Value::Ref(connect)) 208 | } else { 209 | None 210 | } 211 | } 212 | "publish" => { 213 | if let Self::Publish(publish) = self { 214 | Some(Value::Ref(publish)) 215 | } else { 216 | None 217 | } 218 | } 219 | "subscribe" => { 220 | if let Self::Subscribe(subscribe) = self { 221 | Some(Value::Ref(subscribe)) 222 | } else { 223 | None 224 | } 225 | } 226 | "receive" => { 227 | if let Self::Receive(receive) = self { 228 | Some(Value::Ref(receive)) 229 | } else { 230 | None 231 | } 232 | } 233 | _ => None, 234 | } 235 | } else { 236 | None 237 | } 238 | } 239 | } 240 | 241 | impl ToValue for Operation { 242 | fn to_value(&self) -> Value<'_> { 243 | let mut obj = Map::new(); 244 | match self { 245 | Self::Connect(c) => obj.insert(Value::from("connect"), c.to_value()), 246 | Self::Publish(p) => obj.insert(Value::from("publish"), p.to_value()), 247 | Self::Subscribe(s) => obj.insert(Value::from("subscribe"), s.to_value()), 248 | Self::Receive(r) => obj.insert(Value::from("receive"), r.to_value()), 249 | }; 250 | Value::Object(obj) 251 | } 252 | } 253 | 254 | /// Represents a client attempt to connect to the broker. 255 | #[derive(Serialize, Deserialize, Debug)] 256 | #[serde(rename_all = "snake_case")] 257 | pub struct Connect { 258 | remote_addr: IpAddr, 259 | will: Option, 260 | } 261 | 262 | impl Index for Connect { 263 | fn index(&self, field: &Value<'_>) -> Option> { 264 | if let Value::String(s) = field { 265 | match s.as_ref() { 266 | "remote_addr" => Some(Value::String(Cow::Owned(self.remote_addr.to_string()))), 267 | "will" => Some(Value::Ref(&self.will)), 268 | _ => None, 269 | } 270 | } else { 271 | None 272 | } 273 | } 274 | } 275 | 276 | impl ToValue for Connect { 277 | fn to_value(&self) -> Value<'_> { 278 | let mut obj = Map::new(); 279 | obj.insert( 280 | Value::String(Cow::Borrowed("remote_addr")), 281 | Value::String(Cow::Owned(self.remote_addr.to_string())), 282 | ); 283 | obj.insert(Value::String(Cow::Borrowed("will")), self.will.to_value()); 284 | Value::Object(obj) 285 | } 286 | } 287 | 288 | /// Represents a publication description without payload to be used for authorization. 289 | #[derive(Serialize, Deserialize, Debug)] 290 | #[serde(rename_all = "snake_case")] 291 | pub struct Publication { 292 | // topic_name: String, 293 | // qos: proto::QoS, 294 | // retain: bool, 295 | } 296 | 297 | // impl Publication { 298 | // pub fn topic_name(&self) -> &str { 299 | // &self.topic_name 300 | // } 301 | // } 302 | // 303 | // impl From for Publication { 304 | // fn from(publication: proto::Publication) -> Self { 305 | // Self { 306 | // topic_name: publication.topic_name, 307 | // qos: publication.qos, 308 | // retain: publication.retain, 309 | // } 310 | // } 311 | // } 312 | 313 | impl Index for Publication { 314 | fn index(&self, _field: &Value<'_>) -> Option> { 315 | None 316 | } 317 | } 318 | 319 | impl ToValue for Publication { 320 | fn to_value(&self) -> Value<'_> { 321 | Value::Null 322 | } 323 | } 324 | 325 | /// Represents a client attempt to publish a new message on a specified MQTT topic. 326 | #[derive(Serialize, Deserialize, Debug)] 327 | #[serde(rename_all = "snake_case")] 328 | pub struct Publish { 329 | // publication: Publication, 330 | } 331 | 332 | // impl Publish { 333 | // pub fn publication(&self) -> &Publication { 334 | // &self.publication 335 | // } 336 | // } 337 | // 338 | // impl From for Publish { 339 | // fn from(publish: proto::Publish) -> Self { 340 | // Self { 341 | // publication: Publication { 342 | // topic_name: publish.topic_name, 343 | // qos: match publish.packet_identifier_dup_qos { 344 | // proto::PacketIdentifierDupQoS::AtMostOnce => proto::QoS::AtMostOnce, 345 | // proto::PacketIdentifierDupQoS::AtLeastOnce(_, _) => proto::QoS::AtLeastOnce, 346 | // proto::PacketIdentifierDupQoS::ExactlyOnce(_, _) => proto::QoS::ExactlyOnce, 347 | // }, 348 | // retain: publish.retain, 349 | // }, 350 | // } 351 | // } 352 | // } 353 | 354 | impl Index for Publish { 355 | fn index(&self, _field: &Value<'_>) -> Option> { 356 | None 357 | } 358 | } 359 | 360 | impl ToValue for Publish { 361 | fn to_value(&self) -> Value<'_> { 362 | Value::Null 363 | } 364 | } 365 | 366 | /// Represents a client attempt to subscribe to a specified MQTT topic in order to received messages. 367 | #[derive(Serialize, Deserialize, Debug)] 368 | #[serde(rename_all = "snake_case")] 369 | pub struct Subscribe { 370 | // topic_filter: String, 371 | // qos: proto::QoS, 372 | } 373 | 374 | // impl Subscribe { 375 | // pub fn topic_filter(&self) -> &str { 376 | // &self.topic_filter 377 | // } 378 | // } 379 | // 380 | // impl From for Subscribe { 381 | // fn from(subscribe_to: proto::SubscribeTo) -> Self { 382 | // Self { 383 | // topic_filter: subscribe_to.topic_filter, 384 | // qos: subscribe_to.qos, 385 | // } 386 | // } 387 | // } 388 | 389 | impl Index for Subscribe { 390 | fn index(&self, _field: &Value<'_>) -> Option> { 391 | None 392 | } 393 | } 394 | 395 | impl ToValue for Subscribe { 396 | fn to_value(&self) -> Value<'_> { 397 | Value::Null 398 | } 399 | } 400 | 401 | /// Represents a client to received a message from a specified MQTT topic. 402 | #[derive(Serialize, Deserialize, Debug)] 403 | #[serde(rename_all = "snake_case")] 404 | pub struct Receive { 405 | // publication: Publication, 406 | } 407 | 408 | // impl From for Receive { 409 | // fn from(publication: proto::Publication) -> Self { 410 | // Self { 411 | // publication: publication.into(), 412 | // } 413 | // } 414 | // } 415 | 416 | impl Index for Receive { 417 | fn index(&self, _field: &Value<'_>) -> Option> { 418 | None 419 | } 420 | } 421 | 422 | impl ToValue for Receive { 423 | fn to_value(&self) -> Value<'_> { 424 | Value::Null 425 | } 426 | } 427 | 428 | fn main() { 429 | let query = "data.test.allow"; 430 | let mut module_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 431 | module_path.push("examples/activity.rego"); 432 | let module = std::fs::read_to_string(&module_path).unwrap(); 433 | 434 | let mut rego = opa_rego::Policy::from_query(query, &[module.as_str()]).unwrap(); 435 | 436 | for _i in 0..100_000_000 { 437 | let operation = Operation::new_connect(); 438 | let activity = Activity::new( 439 | "auth_id".to_string(), 440 | ClientId("client_id".to_string()), 441 | operation, 442 | ); 443 | let result: bool = rego.evaluate(activity).unwrap(); 444 | assert_eq!(true, result); 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /opa-bench/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /opa-go-sys/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /opa-go-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "opa-go-sys" 3 | version = "0.1.0" 4 | authors = ["Mike Yagley "] 5 | edition = "2018" 6 | 7 | [build-dependencies] 8 | bindgen = "0.53" 9 | gobuild = "0.1.0-alpha.2" 10 | -------------------------------------------------------------------------------- /opa-go-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | fn main() { 5 | let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 6 | let file = root.join("opa.go"); 7 | gobuild::Build::new().file(&file).compile("opa"); 8 | 9 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 10 | let header = out_path.join("libopa.h"); 11 | let bindings = bindgen::Builder::default() 12 | .header(header.display().to_string()) 13 | .parse_callbacks(Box::new(bindgen::CargoCallbacks)) 14 | .whitelist_function("Free") 15 | .whitelist_function("RegoNew") 16 | .whitelist_function("RegoDrop") 17 | .whitelist_function("RegoEval") 18 | .whitelist_function("RegoEvalBool") 19 | .whitelist_function("WasmBuild") 20 | .clang_arg("-I/usr/arm-linux-gnueabihf/include") 21 | .generate() 22 | .expect("Unable to generate bindings"); 23 | 24 | bindings 25 | .write_to_file(out_path.join("bindings.rs")) 26 | .expect("Couldn't write bindings!"); 27 | } 28 | -------------------------------------------------------------------------------- /opa-go-sys/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/myagley/opa-rs/opa-compiler-sys 2 | 3 | go 1.14 4 | 5 | require github.com/open-policy-agent/opa v0.18.0 6 | -------------------------------------------------------------------------------- /opa-go-sys/go.sum: -------------------------------------------------------------------------------- 1 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 2 | github.com/OneOfOne/xxhash v1.2.7 h1:fzrmmkskv067ZQbd9wERNGuxckWw67dyzoMG62p7LMo= 3 | github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 4 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 5 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 8 | github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4 h1:bRzFpEzvausOAt4va+I/22BZ1vXDtERngp0BNYDKej0= 9 | github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 10 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 11 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 12 | github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 13 | github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 14 | github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 15 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 16 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 17 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 18 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 19 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 20 | github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 21 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 22 | github.com/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04= 23 | github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 24 | github.com/open-policy-agent/opa v0.18.0 h1:EC81mO3/517Kq5brJHydqKE5MLzJ+4cdJvUQKxLzHy8= 25 | github.com/open-policy-agent/opa v0.18.0/go.mod h1:6pC1cMYDI92i9EY/GoA2m+HcZlcCrh3jbfny5F7JVTA= 26 | github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= 27 | github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0 h1:R+lX9nKwNd1n7UE5SQAyoorREvRn3aLF6ZndXBoIWqY= 28 | github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 30 | github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 31 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 32 | github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 33 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 34 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= 35 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 36 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 37 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 38 | github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 39 | github.com/spf13/pflag v0.0.0-20181024212040-082b515c9490/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 40 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 41 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 42 | github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= 43 | github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= 44 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 45 | golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 46 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 47 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 48 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 49 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 50 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 51 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 52 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 53 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 54 | golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 55 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 56 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 57 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 60 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 61 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 62 | -------------------------------------------------------------------------------- /opa-go-sys/opa.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #include 4 | import "C" 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "os" 10 | "sync" 11 | "unsafe" 12 | 13 | "github.com/open-policy-agent/opa/loader" 14 | "github.com/open-policy-agent/opa/rego" 15 | ) 16 | 17 | var ( 18 | registry = make(map[uint64]*rego.PreparedEvalQuery) 19 | mutex = &sync.Mutex{} 20 | ids uint64 = 0 21 | ) 22 | 23 | //export RegoNew 24 | func RegoNew(query string, modulename string, modulecontent string) (uint64, *C.char) { 25 | ctx := context.Background() 26 | 27 | prepared, err := rego.New( 28 | rego.Query(query), 29 | rego.Module(modulename, modulecontent), 30 | ).PrepareForEval(ctx) 31 | 32 | if err != nil { 33 | return 0, C.CString(err.Error()) 34 | } 35 | 36 | mutex.Lock() 37 | ids += 1 38 | var id = ids 39 | registry[ids] = &prepared 40 | mutex.Unlock() 41 | 42 | return id, nil 43 | } 44 | 45 | //export RegoDrop 46 | func RegoDrop(id uint64) { 47 | delete(registry, id) 48 | } 49 | 50 | //export RegoEvalBool 51 | func RegoEvalBool(id uint64, inputstr string) (bool, *C.char) { 52 | ctx := context.Background() 53 | 54 | mutex.Lock() 55 | query, found := registry[id] 56 | mutex.Unlock() 57 | 58 | if !found { 59 | return false, C.CString("could not find rego query") 60 | } 61 | 62 | var input interface{} 63 | bytes := []byte(inputstr) 64 | err := json.Unmarshal(bytes, &input) 65 | if err != nil { 66 | return false, C.CString(err.Error()) 67 | } 68 | 69 | results, err := query.Eval(ctx, rego.EvalInput(input)) 70 | if err != nil { 71 | return false, C.CString(err.Error()) 72 | } else if len(results) == 0 { 73 | return false, nil 74 | } else if len(results[0].Expressions) > 0 { 75 | if b, ok := results[0].Expressions[0].Value.(bool); ok { 76 | return b, nil 77 | } else { 78 | return false, nil 79 | } 80 | } else { 81 | return false, nil 82 | } 83 | } 84 | 85 | //export RegoEval 86 | func RegoEval(id uint64, inputstr string) (*C.char, *C.char) { 87 | ctx := context.Background() 88 | 89 | mutex.Lock() 90 | query, found := registry[id] 91 | mutex.Unlock() 92 | 93 | if !found { 94 | return nil, C.CString("could not find rego query") 95 | } 96 | 97 | var input interface{} 98 | bytes := []byte(inputstr) 99 | err := json.Unmarshal(bytes, &input) 100 | if err != nil { 101 | return nil, C.CString(err.Error()) 102 | } 103 | 104 | results, err := query.Eval(ctx, rego.EvalInput(input)) 105 | if err != nil { 106 | return nil, C.CString(err.Error()) 107 | } 108 | 109 | jbytes, err := json.Marshal(results) 110 | if err != nil { 111 | return nil, C.CString(err.Error()) 112 | } 113 | 114 | return C.CString(string(jbytes)), nil 115 | } 116 | 117 | // Wasm 118 | 119 | type loaderFilter struct { 120 | Ignore []string 121 | } 122 | 123 | func (f loaderFilter) Apply(abspath string, info os.FileInfo, depth int) bool { 124 | for _, s := range f.Ignore { 125 | if loader.GlobExcludeName(s, 1)(abspath, info, depth) { 126 | return true 127 | } 128 | } 129 | return false 130 | } 131 | 132 | //export WasmBuild 133 | func WasmBuild(query string, data, bundles, ignore []string) (unsafe.Pointer, int, *C.char) { 134 | ctx := context.Background() 135 | 136 | f := loaderFilter{ 137 | Ignore: ignore, 138 | } 139 | 140 | regoArgs := []func(*rego.Rego){ 141 | rego.Query(query), 142 | } 143 | 144 | if len(data) > 0 { 145 | regoArgs = append(regoArgs, rego.Load(data, f.Apply)) 146 | } 147 | 148 | if len(bundles) > 0 { 149 | for _, bundleDir := range bundles { 150 | regoArgs = append(regoArgs, rego.LoadBundle(bundleDir)) 151 | } 152 | } 153 | 154 | r := rego.New(regoArgs...) 155 | cr, err := r.Compile(ctx, rego.CompilePartial(false)) 156 | if err != nil { 157 | return nil, 0, C.CString(err.Error()) 158 | } 159 | 160 | return C.CBytes(cr.Bytes), len(cr.Bytes), nil 161 | } 162 | 163 | //export Free 164 | func Free(ptr unsafe.Pointer) { 165 | C.free(ptr) 166 | } 167 | 168 | func main() {} 169 | -------------------------------------------------------------------------------- /opa-go-sys/opa_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestRegoNew(t *testing.T) { 6 | query := "data.example.allow" 7 | modulename := "example.rego" 8 | modulecontent := `package example 9 | 10 | default allow = false` 11 | 12 | n, err := RegoNew(query, modulename, modulecontent) 13 | if err != nil { 14 | t.Errorf("err is not nil: %v", err) 15 | } 16 | 17 | if n != 1 { 18 | t.Errorf("first id: got %d, expected %d", n, 1) 19 | } 20 | 21 | n2, err := RegoNew(query, modulename, modulecontent) 22 | if err != nil { 23 | t.Errorf("err is not nil: %v", err) 24 | } 25 | 26 | if n2 != 2 { 27 | t.Errorf("second id: got %d, expected %d", n2, 2) 28 | } 29 | 30 | if len(registry) != 2 { 31 | t.Errorf("registry length: got %d, expected %d", len(registry), 2) 32 | } 33 | 34 | // Drop one 35 | RegoDrop(2) 36 | 37 | if len(registry) != 1 { 38 | t.Errorf("registry length: got %d, expected %d", len(registry), 1) 39 | } 40 | } 41 | 42 | func TestRegoEvalBool_true(t *testing.T) { 43 | query := "data.example.allow" 44 | modulename := "example.rego" 45 | modulecontent := `package example 46 | 47 | default allow = true` 48 | 49 | id, err := RegoNew(query, modulename, modulecontent) 50 | if err != nil { 51 | t.Errorf("err is not nil: %v", err) 52 | } 53 | 54 | isdefined, err := RegoEvalBool(id, `{"test": 1, "a": false}`) 55 | if err != nil { 56 | t.Errorf("err is not nil: %v", err) 57 | } 58 | 59 | expected := true 60 | if isdefined != expected { 61 | t.Errorf("isdefined: got %v, expected %v", isdefined, expected) 62 | } 63 | } 64 | 65 | func TestRegoEvalBool_false(t *testing.T) { 66 | query := "data.example.allow" 67 | modulename := "example.rego" 68 | modulecontent := `package example 69 | 70 | default allow = false` 71 | 72 | id, err := RegoNew(query, modulename, modulecontent) 73 | if err != nil { 74 | t.Errorf("err is not nil: %v", err) 75 | } 76 | 77 | isdefined, err := RegoEvalBool(id, `{"test": 1, "a": false}`) 78 | if err != nil { 79 | t.Errorf("err is not nil: %v", err) 80 | } 81 | 82 | expected := false 83 | if isdefined != expected { 84 | t.Errorf("isdefined: got %v, expected %v", isdefined, expected) 85 | } 86 | } 87 | 88 | func TestRegoEvalBool_undefined(t *testing.T) { 89 | query := "data.example.allow" 90 | modulename := "example.rego" 91 | modulecontent := `package example` 92 | 93 | id, err := RegoNew(query, modulename, modulecontent) 94 | if err != nil { 95 | t.Errorf("err is not nil: %v", err) 96 | } 97 | 98 | isdefined, err := RegoEvalBool(id, `{"test": 1, "a": false}`) 99 | if err != nil { 100 | t.Errorf("err is not nil: %v", err) 101 | } 102 | 103 | expected := false 104 | if isdefined != expected { 105 | t.Errorf("isdefined: got %v, expected %v", isdefined, expected) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /opa-go-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 6 | -------------------------------------------------------------------------------- /opa-go/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "opa-go" 3 | version = "0.1.0" 4 | authors = ["Mike Yagley "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | opa-go-sys = { version = "0.1.0", path = "../opa-go-sys" } 9 | serde = "1" 10 | serde_json = "1" 11 | -------------------------------------------------------------------------------- /opa-go/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::os::raw::{c_char, c_void}; 3 | use std::{error, fmt}; 4 | 5 | use opa_go_sys::*; 6 | use serde::Serialize; 7 | 8 | pub mod wasm; 9 | 10 | #[derive(Debug)] 11 | pub struct Error { 12 | message: String, 13 | } 14 | 15 | impl Error { 16 | fn new(message: String) -> Self { 17 | Self { message } 18 | } 19 | } 20 | 21 | impl fmt::Display for Error { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | write!(f, "error compiling to wasm: {}", self.message) 24 | } 25 | } 26 | 27 | impl error::Error for Error { 28 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 29 | None 30 | } 31 | } 32 | 33 | struct GoError { 34 | ptr: *const c_char, 35 | } 36 | 37 | impl Drop for GoError { 38 | fn drop(&mut self) { 39 | if !self.ptr.is_null() { 40 | unsafe { Free(self.ptr as *mut c_void) } 41 | } 42 | } 43 | } 44 | 45 | impl From for Error { 46 | fn from(error: GoError) -> Self { 47 | let message = unsafe { CStr::from_ptr(error.ptr).to_string_lossy().into_owned() }; 48 | Self { message } 49 | } 50 | } 51 | 52 | pub struct Rego { 53 | id: u64, 54 | } 55 | 56 | impl Rego { 57 | pub fn new(query: &str, module_name: &str, module_contents: &str) -> Result { 58 | let query = GoString { 59 | p: query.as_ptr() as *const c_char, 60 | n: query.len() as isize, 61 | }; 62 | 63 | let module_name = GoString { 64 | p: module_name.as_ptr() as *const c_char, 65 | n: module_name.len() as isize, 66 | }; 67 | 68 | let module_contents = GoString { 69 | p: module_contents.as_ptr() as *const c_char, 70 | n: module_contents.len() as isize, 71 | }; 72 | 73 | let result = unsafe { RegoNew(query, module_name, module_contents) }; 74 | if !result.r1.is_null() { 75 | let e = GoError { 76 | ptr: result.r1 as *const c_char, 77 | }; 78 | return Err(Error::from(e)); 79 | } 80 | 81 | let rego = Self { id: result.r0 }; 82 | Ok(rego) 83 | } 84 | 85 | pub fn eval_bool(&self, input: &T) -> Result { 86 | let serialized = serde_json::to_string(input).map_err(|e| Error::new(e.to_string()))?; 87 | let input = GoString { 88 | p: serialized.as_ptr() as *const c_char, 89 | n: serialized.len() as isize, 90 | }; 91 | let result = unsafe { RegoEvalBool(self.id, input) }; 92 | if !result.r1.is_null() { 93 | let e = GoError { 94 | ptr: result.r1 as *const c_char, 95 | }; 96 | return Err(Error::from(e)); 97 | } 98 | Ok(result.r0 != 0) 99 | } 100 | } 101 | 102 | impl Drop for Rego { 103 | fn drop(&mut self) { 104 | unsafe { RegoDrop(self.id) } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /opa-go/src/wasm.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::{c_char, c_void}; 2 | use std::path::Path; 3 | use std::{slice, str}; 4 | 5 | use opa_go_sys::{Free, GoInt, GoSlice, GoString, WasmBuild}; 6 | 7 | use crate::{Error, GoError}; 8 | 9 | struct WasmBuildReturn { 10 | ptr: *const u8, 11 | len: usize, 12 | } 13 | 14 | impl WasmBuildReturn { 15 | fn into_bytes(self) -> Vec { 16 | let bytes = unsafe { 17 | if self.ptr.is_null() { 18 | vec![] 19 | } else { 20 | let b = slice::from_raw_parts(self.ptr, self.len); 21 | Vec::from(b) 22 | } 23 | }; 24 | bytes 25 | } 26 | } 27 | 28 | impl Drop for WasmBuildReturn { 29 | fn drop(&mut self) { 30 | if !self.ptr.is_null() { 31 | unsafe { Free(self.ptr as *mut c_void) } 32 | } 33 | } 34 | } 35 | 36 | pub fn compile>(query: &str, data: P) -> Result, Error> { 37 | let query = GoString { 38 | p: query.as_ptr() as *const c_char, 39 | n: query.len() as isize, 40 | }; 41 | 42 | let data = data.as_ref().to_str().unwrap(); 43 | let mut data = GoString { 44 | p: data.as_ptr() as *const c_char, 45 | n: data.len() as isize, 46 | }; 47 | let data = slice::from_mut(&mut data); 48 | let data = GoSlice { 49 | data: data.as_mut_ptr() as *mut c_void, 50 | len: data.len() as GoInt, 51 | cap: data.len() as GoInt, 52 | }; 53 | 54 | let bundles = GoSlice { 55 | data: std::ptr::null_mut() as *mut c_void, 56 | len: 0, 57 | cap: 0, 58 | }; 59 | 60 | let ignore = GoSlice { 61 | data: std::ptr::null_mut() as *mut c_void, 62 | len: 0, 63 | cap: 0, 64 | }; 65 | 66 | let bytes = build(query, data, bundles, ignore)?.into_bytes(); 67 | Ok(bytes) 68 | } 69 | 70 | fn build( 71 | query: GoString, 72 | data: GoSlice, 73 | bundles: GoSlice, 74 | ignore: GoSlice, 75 | ) -> Result { 76 | let result = unsafe { WasmBuild(query, data, bundles, ignore) }; 77 | if !result.r0.is_null() && !result.r2.is_null() { 78 | let r = WasmBuildReturn { 79 | ptr: result.r0 as *const u8, 80 | len: result.r1 as usize, 81 | }; 82 | let goe = GoError { 83 | ptr: result.r2 as *const c_char, 84 | }; 85 | drop(goe); 86 | Ok(r) 87 | } else if !result.r2.is_null() { 88 | let goe = GoError { 89 | ptr: result.r2 as *const c_char, 90 | }; 91 | Err(Error::from(goe)) 92 | } else if !result.r0.is_null() { 93 | let r = WasmBuildReturn { 94 | ptr: result.r0 as *const u8, 95 | len: result.r1 as usize, 96 | }; 97 | Ok(r) 98 | } else { 99 | let message = "Result and error pointers are both null.".to_string(); 100 | let e = Error { message }; 101 | Err(e) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /opa-go/tests/compile.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | #[test] 5 | fn test_opa_compiler_compile() { 6 | let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 7 | 8 | let bytes = opa_go::wasm::compile("data.tests.allow", &root.join("tests/empty.rego")).unwrap(); 9 | let expected = fs::read(&root.join("tests/empty.wasm")).unwrap(); 10 | assert_eq!(expected, bytes); 11 | } 12 | -------------------------------------------------------------------------------- /opa-go/tests/empty.rego: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | default allow = false 4 | -------------------------------------------------------------------------------- /opa-go/tests/empty.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myagley/opa-rs/f85db53bd3a5b83e79837ef8b55c7e05adfef245/opa-go/tests/empty.wasm -------------------------------------------------------------------------------- /opa-rego/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "opa-rego" 3 | version = "0.1.0" 4 | authors = ["Mike Yagley "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rego = { path = "../../rego" } 11 | serde = "1" 12 | -------------------------------------------------------------------------------- /opa-rego/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use rego::{CompiledQuery, ValueRef}; 4 | use serde::de::DeserializeOwned; 5 | 6 | #[derive(Debug)] 7 | pub enum Error { 8 | Compile(String), 9 | Runtime(rego::Error<'static>), 10 | Serialize(rego::Error<'static>), 11 | Deserialize(rego::Error<'static>), 12 | } 13 | 14 | impl fmt::Display for Error { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | match self { 17 | Self::Compile(s) => write!(f, "Policy failed to compile: {}", s), 18 | Self::Runtime(_) => write!(f, "An error occurred while evaluating the policy."), 19 | Self::Serialize(_) => write!(f, "An error occurred while serializing the input."), 20 | Self::Deserialize(_) => write!(f, "An error occurred while deserializing the result."), 21 | } 22 | } 23 | } 24 | 25 | impl std::error::Error for Error { 26 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 27 | match self { 28 | Self::Runtime(e) => Some(e), 29 | Self::Serialize(e) => Some(e), 30 | Self::Deserialize(e) => Some(e), 31 | _ => None, 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct Policy { 38 | query: CompiledQuery, 39 | } 40 | 41 | impl Policy { 42 | pub fn from_query(query: &str, modules: &[&str]) -> Result { 43 | let query = rego::compile(query, modules).map_err(|e| Error::Compile(e.to_string()))?; 44 | let policy = Self { query }; 45 | Ok(policy) 46 | } 47 | 48 | pub fn evaluate(&mut self, input: T) -> Result { 49 | let result = self.query.eval(&input).map_err(Error::Runtime)?; 50 | let result = rego::from_value(result).map_err(Error::Deserialize)?; 51 | Ok(result) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn it_works() { 61 | let module = r###" 62 | package test 63 | 64 | default allow = true 65 | "###; 66 | let query = "data.test.allow"; 67 | let mut policy = Policy::from_query(query, &[module]).unwrap(); 68 | let result = policy.evaluate(()).unwrap(); 69 | assert_eq!(true, result); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /opa-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "opa-wasm" 3 | version = "0.1.0" 4 | authors = ["Mike Yagley "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | chrono = "0.4" 11 | chrono-tz = "0.5" 12 | ipnetwork = "0.16" 13 | lazy_static = "1" 14 | ordered-float = { version = "1.0.2", features = ["serde"] } 15 | regex = "1" 16 | serde = "1" 17 | thiserror = "1.0" 18 | tracing = "0.1" 19 | 20 | [target.'cfg(target_arch = "x86_64")'.dependencies] 21 | anyhow = "1.0" 22 | wasmtime = "0.12.0" 23 | 24 | [target.'cfg(not(target_arch = "x86_64"))'.dependencies] 25 | wasmi = "0.6" 26 | 27 | [dev-dependencies] 28 | anyhow = "1.0" 29 | atty = "0.2" 30 | clap = "2.33" 31 | opa-go = { version = "0.1.0", path = "../opa-go" } 32 | proptest = "0.9" 33 | serde_json = "1.0" 34 | tracing = "0.1" 35 | tracing-subscriber = "0.2" 36 | -------------------------------------------------------------------------------- /opa-wasm/examples/eval.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io}; 2 | 3 | use clap::{App, Arg}; 4 | use opa_wasm::{Policy, Value}; 5 | use tracing::Level; 6 | use tracing_subscriber::{fmt, EnvFilter}; 7 | 8 | fn main() -> Result<(), anyhow::Error> { 9 | let subscriber = fmt::Subscriber::builder() 10 | .with_ansi(atty::is(atty::Stream::Stderr)) 11 | .with_max_level(Level::TRACE) 12 | .with_writer(io::stderr) 13 | .with_env_filter(EnvFilter::from_default_env()) 14 | .finish(); 15 | let _ = tracing::subscriber::set_global_default(subscriber); 16 | 17 | let matches = App::new("policy") 18 | .arg( 19 | Arg::with_name("policy") 20 | .short("p") 21 | .long("policy") 22 | .value_name("FILE") 23 | .help("Sets the location of the rego policy file") 24 | .takes_value(true) 25 | .required(true), 26 | ) 27 | .arg( 28 | Arg::with_name("query") 29 | .short("q") 30 | .long("query") 31 | .value_name("QUERY") 32 | .help("Sets the rego query to evaluate") 33 | .takes_value(true) 34 | .required(true), 35 | ) 36 | .arg( 37 | Arg::with_name("input") 38 | .short("i") 39 | .long("input") 40 | .value_name("FILE") 41 | .help("Sets the input file path") 42 | .takes_value(true), 43 | ) 44 | .get_matches(); 45 | 46 | let policy_path = matches.value_of("policy").expect("required policy"); 47 | let query = matches.value_of("query").expect("required query"); 48 | let input_str = matches 49 | .value_of_os("input") 50 | .map(fs::read_to_string) 51 | .unwrap_or_else(|| Ok("{}".to_string()))?; 52 | let input = serde_json::from_str::(&input_str)?; 53 | 54 | let module = opa_go::wasm::compile(query, &policy_path)?; 55 | let mut policy = Policy::from_wasm(&module)?; 56 | let result = policy.evaluate(&input)?; 57 | println!("result: {}", result); 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /opa-wasm/examples/example.rego: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | default allow = false # unless otherwise defined, allow is false */ 4 | 5 | allow = true { # allow is true if... 6 | s1 := { 1, 2}; 7 | s2 := { 1, 3}; 8 | s3 := s1 | s2; 9 | s3 == { 1, 2, 3 } 10 | count(violation) == 0 # there are zero violations. 11 | } 12 | 13 | violation[server.id] { # a server is in the violation set if... 14 | some server 15 | public_server[server] # it exists in the 'public_server' set and... 16 | server.protocols[_] == "http" # it contains the insecure "http" protocol. 17 | } 18 | 19 | violation[server.id] { # a server is in the violation set if... 20 | server := input.servers[_] # it exists in the input.servers collection and... 21 | server.protocols[_] == "telnet" # it contains the "telnet" protocol. 22 | } 23 | 24 | public_server[server] { # a server exists in the public_server set if... 25 | some i, j 26 | server := input.servers[_] # it exists in the input.servers collection and... 27 | server.ports[_] == input.ports[i].id # it references a port in the input.ports collection and... 28 | input.ports[i].network == input.networks[j].id # the port references a network in the input.networks collection and... 29 | input.networks[j].public # the network is public. 30 | } 31 | -------------------------------------------------------------------------------- /opa-wasm/examples/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "id": "app", 5 | "protocols": [ 6 | "https", 7 | "ssh" 8 | ], 9 | "ports": [ 10 | "p1", 11 | "p2", 12 | "p3" 13 | ] 14 | }, 15 | { 16 | "id": "db", 17 | "protocols": [ 18 | "mysql" 19 | ], 20 | "ports": [ 21 | "p3" 22 | ] 23 | }, 24 | { 25 | "id": "cache", 26 | "protocols": [ 27 | "memcache" 28 | ], 29 | "ports": [ 30 | "p3" 31 | ] 32 | } 33 | ], 34 | "networks": [ 35 | { 36 | "id": "net1", 37 | "public": false 38 | }, 39 | { 40 | "id": "net2", 41 | "public": false 42 | }, 43 | { 44 | "id": "net3", 45 | "public": true 46 | }, 47 | { 48 | "id": "net4", 49 | "public": true 50 | } 51 | ], 52 | "ports": [ 53 | { 54 | "id": "p1", 55 | "network": "net1" 56 | }, 57 | { 58 | "id": "p2", 59 | "network": "net3" 60 | }, 61 | { 62 | "id": "p3", 63 | "network": "net2" 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /opa-wasm/proptest-regressions/opa/mod.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc bad5ffc87f6cd4925cdeb3165c5616289cebba7f92d5b6e9976fe5478b1ea8bc # shrinks to input = Array([Array([Number("0")])]) 8 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/aggregates.rs: -------------------------------------------------------------------------------- 1 | use crate::builtins::numbers; 2 | use crate::{Error, Value}; 3 | 4 | pub fn count(val: Value) -> Result { 5 | let v = match val { 6 | Value::Array(ref v) => Value::Number(v.len().into()), 7 | Value::Object(ref v) => Value::Number(v.len().into()), 8 | Value::Set(ref v) => Value::Number(v.len().into()), 9 | Value::String(ref v) => Value::Number(v.len().into()), 10 | val => return Err(Error::InvalidType("collection_or_string", val)), 11 | }; 12 | Ok(v) 13 | } 14 | 15 | macro_rules! binary_loop { 16 | ($name:ident, $init:expr, $op:path) => { 17 | pub fn $name(val: Value) -> Result { 18 | fn do_op>(iter: I) -> Result { 19 | iter.fold(Ok($init.into()), |acc, v| match acc { 20 | Ok(acc) => $op(acc, v), 21 | e => e, 22 | }) 23 | } 24 | 25 | let v = match val { 26 | Value::Array(v) => do_op(v.into_iter())?, 27 | Value::Set(v) => do_op(v.into_iter())?, 28 | val => return Err(Error::InvalidType("collection_or_string", val)), 29 | }; 30 | Ok(v) 31 | } 32 | }; 33 | } 34 | 35 | binary_loop!(sum, 0, numbers::plus); 36 | binary_loop!(product, 1, numbers::mul); 37 | binary_loop!(min, std::f64::MAX, numbers::min); 38 | binary_loop!(max, std::f64::MIN, numbers::max); 39 | binary_loop!(all, true, for_all); 40 | binary_loop!(any, false, for_any); 41 | 42 | fn for_all(left: Value, right: Value) -> Result { 43 | if let (Some(l), Some(r)) = (left.as_bool(), right.as_bool()) { 44 | Ok(Value::Bool(l && r)) 45 | } else { 46 | Ok(Value::Bool(false)) 47 | } 48 | } 49 | 50 | fn for_any(left: Value, right: Value) -> Result { 51 | if let (Some(l), Some(r)) = (left.as_bool(), right.as_bool()) { 52 | Ok(Value::Bool(l || r)) 53 | } else { 54 | Ok(Value::Bool(false)) 55 | } 56 | } 57 | 58 | pub fn sort(val: Value) -> Result { 59 | let v = match val { 60 | Value::Array(mut v) => { 61 | v.sort(); 62 | Value::Array(v) 63 | } 64 | Value::Set(v) => { 65 | let mut vec = v.into_iter().collect::>(); 66 | vec.sort(); 67 | Value::Array(vec) 68 | } 69 | val => return Err(Error::InvalidType("collection_or_string", val)), 70 | }; 71 | Ok(v) 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn test_sum() { 80 | let v: &[u8] = &[1, 2, 3]; 81 | let out = sum(v.into()).unwrap(); 82 | let expected: Value = 6_u8.into(); 83 | assert_eq!(expected, out); 84 | 85 | let v: &[Value] = &[Value::Number(1.into()), Value::String("3".to_string())]; 86 | let out = sum(v.into()); 87 | assert!(out.is_err()); 88 | } 89 | 90 | #[test] 91 | fn test_product() { 92 | let v: &[u8] = &[1, 2, 3, 4]; 93 | let out = product(v.into()).unwrap(); 94 | let expected: Value = 24_u8.into(); 95 | assert_eq!(expected, out); 96 | 97 | let v: &[Value] = &[Value::Number(1.into()), Value::String("3".to_string())]; 98 | let out = product(v.into()); 99 | assert!(out.is_err()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/arrays.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | 3 | use crate::{Error, Value}; 4 | 5 | pub fn concat(left: Value, right: Value) -> Result { 6 | let mut left = left.try_into_array()?; 7 | let mut right = right.try_into_array()?; 8 | left.append(&mut right); 9 | Ok(Value::Array(left)) 10 | } 11 | 12 | pub fn slice(val: Value, start: Value, end: Value) -> Result { 13 | let array = val.try_into_array()?; 14 | let start = start.try_into_i64()?; 15 | let end = end.try_into_i64()?; 16 | 17 | let v = if start >= end || (start < 0 && end < 0) { 18 | Value::Array(vec![]) 19 | } else { 20 | let len = array.len(); 21 | let start = cmp::min(cmp::max(start, 0) as usize, len); 22 | let end = cmp::min(cmp::max(end, 0) as usize, len); 23 | Value::Array(array[start..end].into()) 24 | }; 25 | 26 | Ok(v) 27 | } 28 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::sync::Arc; 4 | 5 | use lazy_static::lazy_static; 6 | use tracing::{debug, error}; 7 | 8 | use crate::runtime::Instance; 9 | use crate::{opa_serde, Error, Value, ValueAddr}; 10 | 11 | mod aggregates; 12 | mod arrays; 13 | mod net; 14 | mod numbers; 15 | mod objects; 16 | mod regex; 17 | mod sets; 18 | mod strings; 19 | mod time; 20 | mod types; 21 | 22 | macro_rules! btry { 23 | ($expr:expr) => { 24 | match $expr { 25 | ::std::result::Result::Ok(val) => val, 26 | ::std::result::Result::Err(err) => { 27 | error!(msg = "error processing builtin function", error = %err); 28 | return ValueAddr(0); 29 | } 30 | } 31 | }; 32 | } 33 | 34 | type Arity0 = fn() -> Result; 35 | type Arity1 = fn(Value) -> Result; 36 | type Arity2 = fn(Value, Value) -> Result; 37 | type Arity3 = fn(Value, Value, Value) -> Result; 38 | type Arity4 = fn(Value, Value, Value, Value) -> Result; 39 | 40 | lazy_static! { 41 | static ref BUILTIN0: HashMap<&'static str, Arity0> = { 42 | let mut b: HashMap<&'static str, Arity0> = HashMap::new(); 43 | b.insert("time.now_ns", time::now_ns); 44 | b 45 | }; 46 | static ref BUILTIN1: HashMap<&'static str, Arity1> = { 47 | let mut b: HashMap<&'static str, Arity1> = HashMap::new(); 48 | b.insert("trace", trace); 49 | 50 | b.insert("all", aggregates::all); 51 | b.insert("any", aggregates::any); 52 | b.insert("count", aggregates::count); 53 | b.insert("max", aggregates::max); 54 | b.insert("min", aggregates::min); 55 | b.insert("product", aggregates::product); 56 | b.insert("sort", aggregates::sort); 57 | b.insert("sum", aggregates::sum); 58 | 59 | b.insert("abs", numbers::abs); 60 | b.insert("round", numbers::round); 61 | 62 | b.insert("net.cidr_expand", net::cidr_expand); 63 | 64 | b.insert("upper", strings::upper); 65 | 66 | b.insert("time.clock", time::clock); 67 | b.insert("time.date", time::date); 68 | b.insert("time.parse_rfc3339_ns", time::parse_rfc3339_ns); 69 | b.insert("time.weekday", time::weekday); 70 | 71 | b.insert("is_array", types::is_array); 72 | b.insert("is_boolean", types::is_boolean); 73 | b.insert("is_null", types::is_null); 74 | b.insert("is_number", types::is_number); 75 | b.insert("is_object", types::is_object); 76 | b.insert("is_set", types::is_set); 77 | b.insert("is_string", types::is_string); 78 | b.insert("type_name", types::type_name); 79 | b 80 | }; 81 | static ref BUILTIN2: HashMap<&'static str, Arity2> = { 82 | let mut b: HashMap<&'static str, Arity2> = HashMap::new(); 83 | b.insert("array.concat", arrays::concat); 84 | 85 | b.insert("plus", numbers::plus); 86 | b.insert("minus", numbers::minus); 87 | b.insert("mul", numbers::mul); 88 | b.insert("div", numbers::div); 89 | b.insert("rem", numbers::rem); 90 | 91 | b.insert("net.cidr_contains", net::cidr_contains); 92 | b.insert("net.cidr_intersects", net::cidr_intersects); 93 | 94 | b.insert("object.remove", objects::remove); 95 | 96 | b.insert("re_match", regex::re_match); 97 | 98 | b.insert("and", sets::and); 99 | b.insert("or", sets::or); 100 | b 101 | }; 102 | static ref BUILTIN3: HashMap<&'static str, Arity3> = { 103 | let mut b: HashMap<&'static str, Arity3> = HashMap::new(); 104 | b.insert("array.slice", arrays::slice); 105 | 106 | b.insert("object.get", objects::get); 107 | b 108 | }; 109 | static ref BUILTIN4: HashMap<&'static str, Arity4> = { 110 | let b: HashMap<&'static str, Arity4> = HashMap::new(); 111 | b 112 | }; 113 | static ref BUILTIN_NAMES: HashSet<&'static str> = { 114 | BUILTIN0 115 | .keys() 116 | .chain(BUILTIN1.keys()) 117 | .chain(BUILTIN2.keys()) 118 | .chain(BUILTIN3.keys()) 119 | .chain(BUILTIN4.keys()) 120 | .map(|k| *k) 121 | .collect::>() 122 | }; 123 | } 124 | 125 | #[derive(Clone, Debug, Default)] 126 | pub struct Builtins { 127 | inner: Arc>>, 128 | } 129 | 130 | impl Builtins { 131 | pub fn replace(&self, instance: Instance) -> Result<(), Error> { 132 | let inner = Inner::new(instance)?; 133 | self.inner.replace(Some(inner)); 134 | Ok(()) 135 | } 136 | 137 | pub fn builtin0(&self, id: i32, ctx_addr: ValueAddr) -> ValueAddr { 138 | let maybe_inner = self.inner.borrow(); 139 | let inner = btry!(maybe_inner.as_ref().ok_or(Error::Initialization)); 140 | inner.builtin0(id, ctx_addr) 141 | } 142 | 143 | pub fn builtin1(&self, id: i32, ctx_addr: ValueAddr, value: ValueAddr) -> ValueAddr { 144 | let maybe_inner = self.inner.borrow(); 145 | let inner = btry!(maybe_inner.as_ref().ok_or(Error::Initialization)); 146 | inner.builtin1(id, ctx_addr, value) 147 | } 148 | 149 | pub fn builtin2(&self, id: i32, ctx_addr: ValueAddr, a: ValueAddr, b: ValueAddr) -> ValueAddr { 150 | let maybe_inner = self.inner.borrow(); 151 | let inner = btry!(maybe_inner.as_ref().ok_or(Error::Initialization)); 152 | inner.builtin2(id, ctx_addr, a, b) 153 | } 154 | 155 | pub fn builtin3( 156 | &self, 157 | id: i32, 158 | ctx_addr: ValueAddr, 159 | a: ValueAddr, 160 | b: ValueAddr, 161 | c: ValueAddr, 162 | ) -> ValueAddr { 163 | let maybe_inner = self.inner.borrow(); 164 | let inner = btry!(maybe_inner.as_ref().ok_or(Error::Initialization)); 165 | inner.builtin3(id, ctx_addr, a, b, c) 166 | } 167 | 168 | pub fn builtin4( 169 | &self, 170 | id: i32, 171 | ctx_addr: ValueAddr, 172 | a: ValueAddr, 173 | b: ValueAddr, 174 | c: ValueAddr, 175 | d: ValueAddr, 176 | ) -> ValueAddr { 177 | let maybe_inner = self.inner.borrow(); 178 | let inner = btry!(maybe_inner.as_ref().ok_or(Error::Initialization)); 179 | inner.builtin4(id, ctx_addr, a, b, c, d) 180 | } 181 | } 182 | 183 | #[derive(Debug)] 184 | struct Inner { 185 | instance: Instance, 186 | lookup: HashMap, 187 | } 188 | 189 | impl Inner { 190 | fn new(instance: Instance) -> Result { 191 | let builtins_addr = instance.functions().builtins()?; 192 | let val: Value = opa_serde::from_instance(&instance, builtins_addr)?; 193 | 194 | let mut lookup = HashMap::new(); 195 | for (k, v) in val.try_into_object()?.into_iter() { 196 | if !BUILTIN_NAMES.contains(k.as_str()) { 197 | return Err(Error::UnknownBuiltin(k)); 198 | } 199 | let v = v.try_into_i64()?; 200 | lookup.insert(v as i32, k); 201 | } 202 | 203 | let inner = Inner { instance, lookup }; 204 | Ok(inner) 205 | } 206 | 207 | fn builtin0(&self, id: i32, _ctx_addr: ValueAddr) -> ValueAddr { 208 | let name = btry!(self 209 | .lookup 210 | .get(&id) 211 | .ok_or_else(|| Error::UnknownBuiltinId(id))); 212 | let func = btry!(BUILTIN0 213 | .get(name.as_str()) 214 | .ok_or_else(|| Error::UnknownBuiltin(name.to_string()))); 215 | debug!(name = %name, arity = 0, "calling builtin function..."); 216 | let result = btry!(func()); 217 | debug!(name = %name, arity = 0, result = ?result, "called builtin function."); 218 | 219 | btry!(opa_serde::to_instance(&self.instance, &result)) 220 | } 221 | 222 | fn builtin1(&self, id: i32, _ctx_addr: ValueAddr, value: ValueAddr) -> ValueAddr { 223 | let name = btry!(self 224 | .lookup 225 | .get(&id) 226 | .ok_or_else(|| Error::UnknownBuiltinId(id))); 227 | let func = btry!(BUILTIN1 228 | .get(name.as_str()) 229 | .ok_or_else(|| Error::UnknownBuiltin(name.to_string()))); 230 | 231 | let val = btry!(opa_serde::from_instance(&self.instance, value)); 232 | 233 | debug!(name = %name, arity = 1, arg0 = ?val, "calling builtin function..."); 234 | let result = btry!(func(val)); 235 | debug!(name = %name, arity = 1, result = ?result, "called builtin function."); 236 | 237 | btry!(opa_serde::to_instance(&self.instance, &result)) 238 | } 239 | 240 | fn builtin2(&self, id: i32, _ctx_addr: ValueAddr, a: ValueAddr, b: ValueAddr) -> ValueAddr { 241 | let name = btry!(self 242 | .lookup 243 | .get(&id) 244 | .ok_or_else(|| Error::UnknownBuiltinId(id))); 245 | let func = btry!(BUILTIN2 246 | .get(name.as_str()) 247 | .ok_or_else(|| Error::UnknownBuiltin(name.to_string()))); 248 | 249 | let val1 = btry!(opa_serde::from_instance(&self.instance, a)); 250 | let val2 = btry!(opa_serde::from_instance(&self.instance, b)); 251 | 252 | debug!(name = %name, arity = 2, arg0 = ?val1, arg1 = ?val2, "calling builtin function..."); 253 | let result = btry!(func(val1, val2)); 254 | debug!(name = %name, arity = 2, result = ?result, "called builtin function."); 255 | 256 | btry!(opa_serde::to_instance(&self.instance, &result)) 257 | } 258 | 259 | fn builtin3( 260 | &self, 261 | id: i32, 262 | _ctx_addr: ValueAddr, 263 | a: ValueAddr, 264 | b: ValueAddr, 265 | c: ValueAddr, 266 | ) -> ValueAddr { 267 | let name = btry!(self 268 | .lookup 269 | .get(&id) 270 | .ok_or_else(|| Error::UnknownBuiltinId(id))); 271 | let func = btry!(BUILTIN3 272 | .get(name.as_str()) 273 | .ok_or_else(|| Error::UnknownBuiltin(name.to_string()))); 274 | 275 | let val1 = btry!(opa_serde::from_instance(&self.instance, a)); 276 | let val2 = btry!(opa_serde::from_instance(&self.instance, b)); 277 | let val3 = btry!(opa_serde::from_instance(&self.instance, c)); 278 | 279 | debug!(name = %name, arity = 3, arg0 = ?val1, arg1 = ?val2, arg2 = ?val3, "calling builtin function..."); 280 | let result = btry!(func(val1, val2, val3)); 281 | debug!(name = %name, arity = 3, result = ?result, "called builtin function."); 282 | 283 | btry!(opa_serde::to_instance(&self.instance, &result)) 284 | } 285 | 286 | fn builtin4( 287 | &self, 288 | id: i32, 289 | _ctx_addr: ValueAddr, 290 | a: ValueAddr, 291 | b: ValueAddr, 292 | c: ValueAddr, 293 | d: ValueAddr, 294 | ) -> ValueAddr { 295 | let name = btry!(self 296 | .lookup 297 | .get(&id) 298 | .ok_or_else(|| Error::UnknownBuiltinId(id))); 299 | let func = btry!(BUILTIN4 300 | .get(name.as_str()) 301 | .ok_or_else(|| Error::UnknownBuiltin(name.to_string()))); 302 | 303 | let val1 = btry!(opa_serde::from_instance(&self.instance, a)); 304 | let val2 = btry!(opa_serde::from_instance(&self.instance, b)); 305 | let val3 = btry!(opa_serde::from_instance(&self.instance, c)); 306 | let val4 = btry!(opa_serde::from_instance(&self.instance, d)); 307 | 308 | debug!(name = %name, arity = 4, arg0 = ?val1, arg1 = ?val2, arg2 = ?val3, arg3 = ?val4, "calling builtin function..."); 309 | let result = btry!(func(val1, val2, val3, val4)); 310 | debug!(name = %name, arity = 4, result = ?result, "called builtin function."); 311 | 312 | btry!(opa_serde::to_instance(&self.instance, &result)) 313 | } 314 | } 315 | 316 | fn trace(value: Value) -> Result { 317 | debug!("TRACE: {:?}", value); 318 | value.try_into_string().map(|_| true.into()) 319 | } 320 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/net.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | 3 | use ipnetwork::IpNetwork; 4 | 5 | use crate::value::Set; 6 | use crate::{Error, Value}; 7 | 8 | enum AddrOrNetwork { 9 | Addr(IpAddr), 10 | Network(IpNetwork), 11 | } 12 | 13 | pub fn cidr_contains(cidr: Value, cidr_or_ip: Value) -> Result { 14 | let cidr = cidr 15 | .try_into_string()? 16 | .parse::() 17 | .map_err(Error::InvalidIpNetwork)?; 18 | let cidr_or_ip = cidr_or_ip.try_into_string()?; 19 | let cidr_or_ip = cidr_or_ip 20 | .parse::() 21 | .map(AddrOrNetwork::Addr) 22 | .or_else(|_| cidr_or_ip.parse::().map(AddrOrNetwork::Network)) 23 | .map_err(Error::InvalidIpNetwork)?; 24 | let v = match (cidr, cidr_or_ip) { 25 | (cidr, AddrOrNetwork::Addr(addr)) => cidr.contains(addr), 26 | (IpNetwork::V4(cidr), AddrOrNetwork::Network(IpNetwork::V4(network))) => { 27 | cidr.is_supernet_of(network) 28 | } 29 | (IpNetwork::V6(cidr), AddrOrNetwork::Network(IpNetwork::V6(network))) => { 30 | cidr.is_supernet_of(network) 31 | } 32 | _ => false, 33 | }; 34 | Ok(v.into()) 35 | } 36 | 37 | pub fn cidr_intersects(cidr1: Value, cidr2: Value) -> Result { 38 | let cidr1 = cidr1 39 | .try_into_string()? 40 | .parse::() 41 | .map_err(Error::InvalidIpNetwork)?; 42 | let cidr2 = cidr2 43 | .try_into_string()? 44 | .parse::() 45 | .map_err(Error::InvalidIpNetwork)?; 46 | let v = match (cidr1, cidr2) { 47 | (IpNetwork::V4(cidr1), IpNetwork::V4(cidr2)) => cidr1.overlaps(cidr2), 48 | (IpNetwork::V6(cidr1), IpNetwork::V6(cidr2)) => cidr1.overlaps(cidr2), 49 | _ => false, 50 | }; 51 | Ok(v.into()) 52 | } 53 | 54 | pub fn cidr_expand(cidr: Value) -> Result { 55 | let cidr = cidr 56 | .try_into_string()? 57 | .parse::() 58 | .map_err(Error::InvalidIpNetwork)?; 59 | let v = cidr 60 | .iter() 61 | .map(|a| a.to_string()) 62 | .map(Into::into) 63 | .collect::>(); 64 | Ok(v.into()) 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_net_cidr_contains() { 73 | let cidr = "127.0.0.1/16".into(); 74 | let ip = "127.0.0.2".into(); 75 | assert_eq!( 76 | true, 77 | cidr_contains(cidr, ip).unwrap().try_into_bool().unwrap() 78 | ); 79 | 80 | let cidr = "127.0.0.1/16".into(); 81 | let net = "127.0.0.1/16".into(); 82 | assert_eq!( 83 | true, 84 | cidr_contains(cidr, net).unwrap().try_into_bool().unwrap() 85 | ); 86 | 87 | let cidr = "127.0.0.1/16".into(); 88 | let ip = "172.18.0.1".into(); 89 | assert_eq!( 90 | false, 91 | cidr_contains(cidr, ip).unwrap().try_into_bool().unwrap() 92 | ); 93 | 94 | let cidr = "127.0.0.1/16".into(); 95 | let net = "127.0.0.1/15".into(); 96 | assert_eq!( 97 | false, 98 | cidr_contains(cidr, net).unwrap().try_into_bool().unwrap() 99 | ); 100 | } 101 | 102 | #[test] 103 | fn test_net_cidr_intersects() { 104 | let cidr1 = "192.168.0.0/16".into(); 105 | let cidr2 = "192.168.1.0/24".into(); 106 | assert_eq!( 107 | true, 108 | cidr_intersects(cidr1, cidr2) 109 | .unwrap() 110 | .try_into_bool() 111 | .unwrap() 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/numbers.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Value}; 2 | 3 | macro_rules! unary_op { 4 | ($name:ident, $op:ident) => { 5 | pub fn $name(val: Value) -> Result { 6 | let v = match val { 7 | val if val.is_i64() => { 8 | let val = val.try_into_i64()?; 9 | let result = val.$op(); 10 | Value::Number(result.into()) 11 | } 12 | Value::Number(val) => { 13 | let val = val.try_into_f64()?; 14 | let result = val.$op(); 15 | Value::Number(result.into()) 16 | } 17 | val => return Err(Error::InvalidType("number", val)), 18 | }; 19 | Ok(v) 20 | } 21 | }; 22 | } 23 | 24 | macro_rules! binary_op { 25 | ($name:ident, $op:tt) => ( 26 | pub fn $name(left: Value, right: Value) -> Result { 27 | let v = match (left, right) { 28 | (left, right) if left.is_i64() && right.is_i64() => { 29 | let left = left.try_into_i64()?; 30 | let right = right.try_into_i64()?; 31 | let result = left $op right; 32 | Value::Number(result.into()) 33 | }, 34 | (Value::Number(left), Value::Number(right)) => { 35 | let left = left.try_into_f64()?; 36 | let right = right.try_into_f64()?; 37 | let result = left $op right; 38 | Value::Number(result.into()) 39 | }, 40 | (a, _) => return Err(Error::InvalidType("number", a)), 41 | }; 42 | Ok(v) 43 | } 44 | ); 45 | } 46 | 47 | macro_rules! binary_op_func { 48 | ($name:ident, $op:tt) => { 49 | pub fn $name(left: Value, right: Value) -> Result { 50 | let v = match (left, right) { 51 | (left, right) if left.is_i64() && right.is_i64() => { 52 | let left = left.try_into_i64()?; 53 | let right = right.try_into_i64()?; 54 | let result = left.$op(right); 55 | Value::Number(result.into()) 56 | } 57 | (Value::Number(left), Value::Number(right)) => { 58 | let left = left.try_into_f64()?; 59 | let right = right.try_into_f64()?; 60 | let result = left.$op(right); 61 | Value::Number(result.into()) 62 | } 63 | (a, _) => return Err(Error::InvalidType("number", a)), 64 | }; 65 | Ok(v) 66 | } 67 | }; 68 | } 69 | 70 | unary_op!(abs, abs); 71 | 72 | binary_op!(plus, +); 73 | binary_op!(mul, *); 74 | binary_op!(div, /); 75 | binary_op!(rem, %); 76 | 77 | binary_op_func!(min, min); 78 | binary_op_func!(max, max); 79 | 80 | pub fn minus(left: Value, right: Value) -> Result { 81 | let v = match (left, right) { 82 | (left, right) if left.is_i64() && right.is_i64() => { 83 | let left = left.try_into_i64()?; 84 | let right = right.try_into_i64()?; 85 | let result = left - right; 86 | Value::Number(result.into()) 87 | } 88 | (Value::Number(left), Value::Number(right)) => { 89 | let left = left.try_into_f64()?; 90 | let right = right.try_into_f64()?; 91 | let result = left - right; 92 | Value::Number(result.into()) 93 | } 94 | (Value::Set(left), Value::Set(right)) => { 95 | Value::Set(left.difference(&right).cloned().collect()) 96 | } 97 | (a, _) => return Err(Error::InvalidType("number", a)), 98 | }; 99 | Ok(v) 100 | } 101 | 102 | pub fn round(val: Value) -> Result { 103 | let v = match val { 104 | val if val.is_i64() => { 105 | let val = val.try_into_i64()?; 106 | Value::Number(val.into()) 107 | } 108 | Value::Number(val) => { 109 | let val = val.try_into_f64()?; 110 | let result = val.round(); 111 | Value::Number(result.into()) 112 | } 113 | val => return Err(Error::InvalidType("Number", val)), 114 | }; 115 | Ok(v) 116 | } 117 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/objects.rs: -------------------------------------------------------------------------------- 1 | use crate::value::Map; 2 | use crate::{Error, Value}; 3 | 4 | pub fn get(object: Value, key: Value, default: Value) -> Result { 5 | let mut object = object.try_into_object()?; 6 | let key = key.try_into_string()?; 7 | let v = object.remove(&key).unwrap_or(default); 8 | Ok(v) 9 | } 10 | 11 | pub fn remove(object: Value, keys: Value) -> Result { 12 | let object = object.try_into_object()?; 13 | match keys { 14 | Value::Array(v) => remove_all(object, v.into_iter()), 15 | Value::Set(v) => remove_all(object, v.into_iter()), 16 | Value::Object(v) => remove_all(object, v.into_iter().map(|(k, _v)| Value::String(k))), 17 | v => Err(Error::InvalidType("iterator of strings", v)), 18 | } 19 | } 20 | 21 | fn remove_all(mut map: Map, iter: I) -> Result 22 | where 23 | I: Iterator, 24 | { 25 | for key in iter { 26 | map.remove(&key.try_into_string()?); 27 | } 28 | Ok(map.into()) 29 | } 30 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/regex.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | use crate::{Error, Value}; 4 | 5 | // TODO - memoize the compilation of the regex 6 | pub fn re_match(pattern: Value, value: Value) -> Result { 7 | let pattern = format!("^{}$", pattern.try_into_string()?); 8 | let regex = Regex::new(&pattern).map_err(Error::InvalidRegex)?; 9 | let value = value.try_into_string()?; 10 | let b = regex.is_match(&value); 11 | Ok(b.into()) 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | 18 | #[test] 19 | fn test_re_match() { 20 | let result = re_match("[a-z]*".into(), "hello".into()) 21 | .unwrap() 22 | .as_bool() 23 | .unwrap(); 24 | assert_eq!(true, result); 25 | 26 | let result = re_match("[a-z]*".into(), "Hello".into()) 27 | .unwrap() 28 | .as_bool() 29 | .unwrap(); 30 | assert_eq!(false, result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/sets.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Value}; 2 | 3 | pub fn and(left: Value, right: Value) -> Result { 4 | let left = left.try_into_set()?; 5 | let right = right.try_into_set()?; 6 | Ok(Value::Set(left.intersection(&right).cloned().collect())) 7 | } 8 | 9 | pub fn or(left: Value, right: Value) -> Result { 10 | let left = left.try_into_set()?; 11 | let right = right.try_into_set()?; 12 | Ok(Value::Set(left.union(&right).cloned().collect())) 13 | } 14 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/strings.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Value}; 2 | 3 | pub fn upper(string: Value) -> Result { 4 | let s = string.try_into_string()?; 5 | Ok(Value::String(s.to_uppercase())) 6 | } 7 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/time.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Datelike, Local, TimeZone, Timelike, Utc, Weekday}; 2 | use chrono_tz::Tz; 3 | 4 | use crate::{Error, Value}; 5 | 6 | pub fn now_ns() -> Result { 7 | Ok(Utc::now().timestamp_nanos().into()) 8 | } 9 | 10 | pub fn date(value: Value) -> Result { 11 | match value { 12 | Value::Number(n) if n.is_i64() => { 13 | let datetime = Utc.timestamp_nanos(n.try_into_i64()?); 14 | Ok(vec![ 15 | datetime.year(), 16 | datetime.month() as i32, 17 | datetime.day() as i32, 18 | ] 19 | .into()) 20 | } 21 | Value::Array(v) => match &v[..] { 22 | [nanos, tz] => { 23 | let nanos = nanos 24 | .as_i64() 25 | .ok_or_else(|| Error::InvalidType("i64", nanos.clone()))?; 26 | let v = match tz 27 | .as_str() 28 | .ok_or_else(|| Error::InvalidType("string", tz.clone()))? 29 | { 30 | "UTC" | "" => { 31 | let datetime = Utc.timestamp_nanos(nanos); 32 | vec![ 33 | datetime.year(), 34 | datetime.month() as i32, 35 | datetime.day() as i32, 36 | ] 37 | } 38 | "Local" => { 39 | let datetime = Local.timestamp_nanos(nanos); 40 | vec![ 41 | datetime.year(), 42 | datetime.month() as i32, 43 | datetime.day() as i32, 44 | ] 45 | } 46 | iana => { 47 | let datetime = iana 48 | .parse::() 49 | .map_err(Error::UnknownTimezone)? 50 | .timestamp_nanos(nanos); 51 | vec![ 52 | datetime.year(), 53 | datetime.month() as i32, 54 | datetime.day() as i32, 55 | ] 56 | } 57 | }; 58 | Ok(v.into()) 59 | } 60 | v => Err(Error::InvalidType("i64 or array[ns, tz]", v.into())), 61 | }, 62 | v => Err(Error::InvalidType("i64 or array[ns, tz]", v)), 63 | } 64 | } 65 | 66 | pub fn clock(value: Value) -> Result { 67 | match value { 68 | Value::Number(n) if n.is_i64() => { 69 | let datetime = Utc.timestamp_nanos(n.try_into_i64()?); 70 | Ok(vec![datetime.hour(), datetime.minute(), datetime.second()].into()) 71 | } 72 | Value::Array(v) => match &v[..] { 73 | [nanos, tz] => { 74 | let nanos = nanos 75 | .as_i64() 76 | .ok_or_else(|| Error::InvalidType("i64", nanos.clone()))?; 77 | let v = match tz 78 | .as_str() 79 | .ok_or_else(|| Error::InvalidType("string", tz.clone()))? 80 | { 81 | "UTC" | "" => { 82 | let datetime = Utc.timestamp_nanos(nanos); 83 | vec![datetime.hour(), datetime.minute(), datetime.second()] 84 | } 85 | "Local" => { 86 | let datetime = Local.timestamp_nanos(nanos); 87 | vec![datetime.hour(), datetime.minute(), datetime.second()] 88 | } 89 | iana => { 90 | let datetime = iana 91 | .parse::() 92 | .map_err(Error::UnknownTimezone)? 93 | .timestamp_nanos(nanos); 94 | vec![datetime.hour(), datetime.minute(), datetime.second()] 95 | } 96 | }; 97 | Ok(v.into()) 98 | } 99 | v => Err(Error::InvalidType("i64 or array[ns, tz]", v.into())), 100 | }, 101 | v => Err(Error::InvalidType("i64 or array[ns, tz]", v)), 102 | } 103 | } 104 | 105 | pub fn weekday(value: Value) -> Result { 106 | match value { 107 | Value::Number(n) if n.is_i64() => { 108 | let datetime = Utc.timestamp_nanos(n.try_into_i64()?); 109 | Ok(vec![datetime.hour(), datetime.minute(), datetime.second()].into()) 110 | } 111 | Value::Array(v) => match &v[..] { 112 | [nanos, tz] => { 113 | let nanos = nanos 114 | .as_i64() 115 | .ok_or_else(|| Error::InvalidType("i64", nanos.clone()))?; 116 | let v = match tz 117 | .as_str() 118 | .ok_or_else(|| Error::InvalidType("string", tz.clone()))? 119 | { 120 | "UTC" | "" => { 121 | let datetime = Utc.timestamp_nanos(nanos); 122 | weekday_to_string(datetime.weekday()) 123 | } 124 | "Local" => { 125 | let datetime = Local.timestamp_nanos(nanos); 126 | weekday_to_string(datetime.weekday()) 127 | } 128 | iana => { 129 | let datetime = iana 130 | .parse::() 131 | .map_err(Error::UnknownTimezone)? 132 | .timestamp_nanos(nanos); 133 | weekday_to_string(datetime.weekday()) 134 | } 135 | }; 136 | Ok(v.into()) 137 | } 138 | v => Err(Error::InvalidType("i64 or array[ns, tz]", v.into())), 139 | }, 140 | v => Err(Error::InvalidType("i64 or array[ns, tz]", v)), 141 | } 142 | } 143 | 144 | fn weekday_to_string(weekday: Weekday) -> String { 145 | match weekday { 146 | Weekday::Mon => "Monday".to_string(), 147 | Weekday::Tue => "Tuesday".to_string(), 148 | Weekday::Wed => "Wednesday".to_string(), 149 | Weekday::Thu => "Thursday".to_string(), 150 | Weekday::Fri => "Friday".to_string(), 151 | Weekday::Sat => "Saturday".to_string(), 152 | Weekday::Sun => "Sunday".to_string(), 153 | } 154 | } 155 | 156 | pub fn parse_rfc3339_ns(value: Value) -> Result { 157 | let string = value.try_into_string()?; 158 | let datetime = DateTime::parse_from_rfc3339(&string).map_err(Error::ParseDatetime)?; 159 | Ok(datetime.timestamp_nanos().into()) 160 | } 161 | -------------------------------------------------------------------------------- /opa-wasm/src/builtins/types.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Value}; 2 | 3 | macro_rules! is_func { 4 | ($func:ident) => { 5 | pub fn $func(val: Value) -> Result { 6 | Ok(val.$func().into()) 7 | } 8 | }; 9 | } 10 | 11 | is_func!(is_number); 12 | is_func!(is_string); 13 | is_func!(is_boolean); 14 | is_func!(is_array); 15 | is_func!(is_set); 16 | is_func!(is_object); 17 | is_func!(is_null); 18 | 19 | pub fn type_name(val: Value) -> Result { 20 | let v = match val { 21 | Value::Null => Value::String("null".to_string()), 22 | Value::Bool(_) => Value::String("bool".to_string()), 23 | Value::Number(_) => Value::String("number".to_string()), 24 | Value::String(_) => Value::String("string".to_string()), 25 | Value::Array(_) => Value::String("array".to_string()), 26 | Value::Object(_) => Value::String("object".to_string()), 27 | Value::Set(_) => Value::String("set".to_string()), 28 | }; 29 | Ok(v) 30 | } 31 | -------------------------------------------------------------------------------- /opa-wasm/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, io}; 2 | 3 | use serde::{de, ser}; 4 | use thiserror::Error; 5 | 6 | #[cfg(target_arch = "x86_64")] 7 | use wasmtime::Trap; 8 | 9 | use crate::{opa_serde, Value}; 10 | 11 | #[derive(Error, Debug)] 12 | pub enum Error { 13 | #[error("Policy is not initialized properly. This is a bug.")] 14 | Initialization, 15 | #[cfg(target_arch = "x86_64")] 16 | #[error("An occurred from wasmtime.")] 17 | Wasmtime(#[source] anyhow::Error), 18 | #[cfg(not(target_arch = "x86_64"))] 19 | #[error("An occurred from wasmi.")] 20 | Wasmi(#[source] wasmi::Error), 21 | #[error("Expected exported function {0}")] 22 | MissingExport(&'static str), 23 | #[cfg(target_arch = "x86_64")] 24 | #[error("A wasm function call trapped.")] 25 | Trap( 26 | #[source] 27 | #[from] 28 | Trap, 29 | ), 30 | #[error("Failed to open a directory.")] 31 | DirOpen(#[source] io::Error), 32 | #[error("Failed to open a file.")] 33 | FileOpen(#[source] io::Error), 34 | #[error("Failed to read file.")] 35 | FileRead(#[source] io::Error), 36 | #[error("Failed to call opa compiler.")] 37 | OpaCommand(#[source] io::Error), 38 | #[error("Failed to compile rego file: {0}")] 39 | OpaCompiler(String), 40 | #[error("Failed to deserialize: {0}")] 41 | DeserializeValue(String), 42 | #[error("Failed to serialize: {0}")] 43 | SerializeValue(String), 44 | #[error("Invalid type in builtin function: expected {0}, got {1:?}")] 45 | InvalidType(&'static str, Value), 46 | #[error("Invalid type conversion in builtin function: expected {0}")] 47 | InvalidConversion(&'static str), 48 | #[error("Unknown builtin required: {0}")] 49 | UnknownBuiltin(String), 50 | #[error("Unknown builtin id: {0}")] 51 | UnknownBuiltinId(i32), 52 | #[error("Unknown timezone: {0}")] 53 | UnknownTimezone(String), 54 | #[error("Failed to parse datetime.")] 55 | ParseDatetime(#[source] chrono::ParseError), 56 | #[error("Invalid ip network.")] 57 | InvalidIpNetwork(#[source] ipnetwork::IpNetworkError), 58 | #[error("Invalid regex.")] 59 | InvalidRegex(#[source] regex::Error), 60 | #[error("Invalid function return. Expected {0}")] 61 | InvalidResult(&'static str), 62 | #[error("Failed to serialize value to instance.")] 63 | InstanceSerde(#[source] opa_serde::Error), 64 | #[error("Invalid buffer length when casting to struct. Expected {0}, got {1}.")] 65 | NotEnoughData(usize, usize), 66 | } 67 | 68 | impl de::Error for Error { 69 | fn custom(msg: T) -> Error { 70 | Error::DeserializeValue(msg.to_string()) 71 | } 72 | } 73 | 74 | impl ser::Error for Error { 75 | fn custom(msg: T) -> Error { 76 | Error::SerializeValue(msg.to_string()) 77 | } 78 | } 79 | 80 | impl From for Error { 81 | fn from(error: opa_serde::Error) -> Error { 82 | Error::InstanceSerde(error) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /opa-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, ops}; 2 | 3 | use serde::Serialize; 4 | 5 | mod builtins; 6 | mod error; 7 | mod opa_serde; 8 | mod runtime; 9 | pub mod set; 10 | pub mod value; 11 | 12 | use runtime::{Instance, Memory, Module}; 13 | use value::Map; 14 | 15 | pub use error::Error; 16 | pub use value::Value; 17 | 18 | #[derive(Copy, Clone, Debug, PartialEq)] 19 | pub struct ValueAddr(i32); 20 | 21 | impl fmt::Display for ValueAddr { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | write!(f, "ValueAddr({})", self.0) 24 | } 25 | } 26 | 27 | impl From for ValueAddr { 28 | fn from(addr: i32) -> Self { 29 | Self(addr) 30 | } 31 | } 32 | 33 | impl From for i32 { 34 | fn from(v: ValueAddr) -> Self { 35 | v.0 36 | } 37 | } 38 | 39 | impl ops::Add for ValueAddr { 40 | type Output = ValueAddr; 41 | 42 | fn add(self, rhs: usize) -> Self { 43 | ValueAddr(self.0 + rhs as i32) 44 | } 45 | } 46 | 47 | #[allow(dead_code)] 48 | pub struct Policy { 49 | instance: Instance, 50 | data_addr: ValueAddr, 51 | base_heap_ptr: ValueAddr, 52 | base_heap_top: ValueAddr, 53 | data_heap_ptr: ValueAddr, 54 | data_heap_top: ValueAddr, 55 | } 56 | 57 | impl Policy { 58 | pub fn from_wasm>(bytes: B) -> Result { 59 | let module = Module::from_bytes(bytes)?; 60 | let memory = Memory::from_module(&module); 61 | let instance = Instance::new(&module, memory)?; 62 | 63 | // Load initial data 64 | let initial = Value::Object(Map::new()); 65 | let data_addr = opa_serde::to_instance(&instance, &initial)?; 66 | 67 | let base_heap_ptr = instance.functions().heap_ptr_get()?; 68 | let base_heap_top = instance.functions().heap_top_get()?; 69 | let data_heap_ptr = base_heap_ptr; 70 | let data_heap_top = base_heap_top; 71 | 72 | let policy = Policy { 73 | instance, 74 | data_addr, 75 | base_heap_ptr, 76 | base_heap_top, 77 | data_heap_ptr, 78 | data_heap_top, 79 | }; 80 | 81 | Ok(policy) 82 | } 83 | 84 | // This takes a &mut self because calling it potentially mutates the 85 | // memory. We could make this take &self, if we add a mutex. 86 | pub fn evaluate(&mut self, input: &T) -> Result { 87 | // Reset the heap pointers 88 | self.instance.functions().heap_ptr_set(self.data_heap_ptr)?; 89 | self.instance.functions().heap_top_set(self.data_heap_top)?; 90 | 91 | // Load input data 92 | let input_addr = opa_serde::to_instance(&self.instance, input)?; 93 | 94 | // setup the context 95 | let ctx_addr = self.instance.functions().eval_ctx_new()?; 96 | self.instance 97 | .functions() 98 | .eval_ctx_set_input(ctx_addr, input_addr)?; 99 | self.instance 100 | .functions() 101 | .eval_ctx_set_data(ctx_addr, self.data_addr)?; 102 | 103 | // Eval 104 | self.instance.functions().eval(ctx_addr)?; 105 | 106 | let result_addr = self.instance.functions().eval_ctx_get_result(ctx_addr)?; 107 | let v = opa_serde::from_instance(&self.instance, result_addr)?; 108 | Ok(v) 109 | } 110 | 111 | pub fn set_data(&mut self, data: &T) -> Result<(), Error> { 112 | self.instance.functions().heap_ptr_set(self.base_heap_ptr)?; 113 | self.instance.functions().heap_top_set(self.base_heap_top)?; 114 | self.data_addr = opa_serde::to_instance(&self.instance, data)?; 115 | self.data_heap_ptr = self.instance.functions().heap_ptr_get()?; 116 | self.data_heap_top = self.instance.functions().heap_top_get()?; 117 | Ok(()) 118 | } 119 | 120 | // TODO: add proper parsing here 121 | // pub fn builtins(&mut self) -> Result { 122 | // let addr = self.instance.functions().builtins()?; 123 | // let s = dump_json(&self.instance, addr)?; 124 | // Ok(s) 125 | // } 126 | } 127 | 128 | fn abort(_a: i32) { 129 | println!("abort"); 130 | } 131 | -------------------------------------------------------------------------------- /opa-wasm/src/opa_serde/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::{convert, fmt, num, string}; 3 | 4 | use serde::{de, ser}; 5 | use thiserror::Error; 6 | 7 | pub type Result = std::result::Result; 8 | 9 | #[derive(Debug, Error)] 10 | pub enum Error { 11 | #[error("{0}")] 12 | Message(String), 13 | #[error("General error.")] 14 | General(#[source] Box), 15 | #[error("Failed to alloc memory.")] 16 | Alloc(#[source] Box), 17 | #[error("Failed to set memory.")] 18 | MemSet(#[source] Box), 19 | #[error("Expected sequence length. Serializer does not support serializing sequences without lengths.")] 20 | ExpectedSeqLen, 21 | #[error("Unexpected null pointer.")] 22 | NullPtr, 23 | #[error("Invalid serialized length. Expected len {0}, serialized {1}")] 24 | InvalidSeqLen(usize, usize), 25 | #[error("Unknown type: {0}")] 26 | UnknownType(u8), 27 | #[error("Expected boolean value. Found type {0}")] 28 | ExpectedBoolean(u8), 29 | #[error("Expected number value. Found type {0}")] 30 | ExpectedNumber(u8), 31 | #[error("Expected integer value. Found repr {0}")] 32 | ExpectedInteger(u8), 33 | #[error("Expected float value. Found repr {0}")] 34 | ExpectedFloat(u8), 35 | #[error("Expected number ref. Found repr {0}")] 36 | ExpectedNumberRef(u8), 37 | #[error("Invalid number repr. Found repr {0}")] 38 | InvalidNumberRepr(u8), 39 | #[error("Integer conversion failed.")] 40 | IntegerConversion(#[source] num::TryFromIntError), 41 | #[error("Expected string value. Found type {0}")] 42 | ExpectedString(u8), 43 | #[error("Invalid utf8 string.")] 44 | InvalidUtf8(#[source] string::FromUtf8Error), 45 | #[error("Invalid char. Expected a string of length one.")] 46 | InvalidChar, 47 | #[error("Expected null value. Found type {0}")] 48 | ExpectedNull(u8), 49 | #[error("Expected array value. Found type {0}")] 50 | ExpectedArray(u8), 51 | #[error("Expected object value. Found type {0}")] 52 | ExpectedObject(u8), 53 | #[error("Expected enum value. Found type {0}")] 54 | ExpectedEnum(u8), 55 | #[error("Expected next address when parsing object element value")] 56 | ExpectedNextAddr, 57 | #[error("Expected entry key when parsing enum.")] 58 | ExpectedKey, 59 | #[error("Expected entry value when parsing enum.")] 60 | ExpectedValue, 61 | #[error("Invalid set found.")] 62 | SetInvalid, 63 | #[error("Invalid number ref found.")] 64 | NumberRefInvalid, 65 | #[error("Expected field {0}.")] 66 | ExpectedField(&'static str), 67 | } 68 | 69 | impl ser::Error for Error { 70 | fn custom(msg: T) -> Self { 71 | Error::Message(msg.to_string()) 72 | } 73 | } 74 | 75 | impl de::Error for Error { 76 | fn custom(msg: T) -> Self { 77 | Error::Message(msg.to_string()) 78 | } 79 | } 80 | 81 | impl From for Error { 82 | fn from(error: num::TryFromIntError) -> Self { 83 | Error::IntegerConversion(error) 84 | } 85 | } 86 | 87 | impl From for Error { 88 | fn from(_error: convert::Infallible) -> Error { 89 | unreachable!() 90 | } 91 | } 92 | 93 | impl From for Error { 94 | fn from(error: crate::Error) -> Self { 95 | Self::General(Box::new(error)) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /opa-wasm/src/opa_serde/mod.rs: -------------------------------------------------------------------------------- 1 | mod de; 2 | mod error; 3 | mod ser; 4 | 5 | pub use de::{from_instance, Deserializer}; 6 | pub use error::{Error, Result}; 7 | pub use ser::{to_instance, Serializer}; 8 | 9 | use std::mem; 10 | use std::os::raw::*; 11 | 12 | use crate::runtime::{AsBytes, FromBytes}; 13 | use crate::ValueAddr; 14 | 15 | const OPA_NULL: c_uchar = 1; 16 | const OPA_BOOLEAN: c_uchar = 2; 17 | const OPA_NUMBER: c_uchar = 3; 18 | const OPA_STRING: c_uchar = 4; 19 | const OPA_ARRAY: c_uchar = 5; 20 | const OPA_OBJECT: c_uchar = 6; 21 | const OPA_SET: c_uchar = 7; 22 | 23 | const OPA_NUMBER_REPR_INT: c_uchar = 1; 24 | const OPA_NUMBER_REPR_FLOAT: c_uchar = 2; 25 | const OPA_NUMBER_REPR_REF: c_uchar = 3; 26 | 27 | const NULL: opa_value = opa_value { ty: OPA_NULL }; 28 | 29 | // wasm is 32-bit and doesn't support unsigned ints 30 | #[allow(non_camel_case_types)] 31 | type size_t = c_int; 32 | #[allow(non_camel_case_types)] 33 | type intptr_t = c_int; 34 | 35 | macro_rules! as_bytes { 36 | ($ty:ty) => { 37 | impl AsBytes for $ty { 38 | fn as_bytes(&self) -> &[u8] { 39 | unsafe { 40 | let slice = std::slice::from_raw_parts(self as *const Self, 1); 41 | std::slice::from_raw_parts( 42 | slice.as_ptr() as *const _, 43 | slice.len() * std::mem::size_of::(), 44 | ) 45 | } 46 | } 47 | } 48 | }; 49 | } 50 | 51 | as_bytes!(opa_value); 52 | as_bytes!(opa_boolean_t); 53 | as_bytes!(opa_number_t); 54 | as_bytes!(opa_string_t); 55 | as_bytes!(opa_array_t); 56 | as_bytes!(opa_array_elem_t); 57 | as_bytes!(opa_object_t); 58 | as_bytes!(opa_object_elem_t); 59 | as_bytes!(opa_set_t); 60 | as_bytes!(opa_set_elem_t); 61 | 62 | unsafe impl FromBytes for opa_value {} 63 | unsafe impl FromBytes for opa_boolean_t {} 64 | unsafe impl FromBytes for opa_number_t {} 65 | unsafe impl FromBytes for opa_string_t {} 66 | unsafe impl FromBytes for opa_array_t {} 67 | unsafe impl FromBytes for opa_array_elem_t {} 68 | unsafe impl FromBytes for opa_object_t {} 69 | unsafe impl FromBytes for opa_object_elem_t {} 70 | unsafe impl FromBytes for opa_set_t {} 71 | unsafe impl FromBytes for opa_set_elem_t {} 72 | 73 | #[repr(C)] 74 | #[derive(Copy, Clone, Debug)] 75 | pub struct opa_value { 76 | pub ty: c_uchar, 77 | } 78 | 79 | #[repr(C)] 80 | #[derive(Copy, Clone, Debug)] 81 | pub struct opa_boolean_t { 82 | pub hdr: opa_value, 83 | pub v: c_int, 84 | } 85 | 86 | impl opa_boolean_t { 87 | pub fn new(b: bool) -> Self { 88 | let v = if b { 1 } else { 0 }; 89 | let hdr = opa_value { ty: OPA_BOOLEAN }; 90 | Self { hdr, v } 91 | } 92 | } 93 | 94 | #[repr(C)] 95 | #[derive(Copy, Clone, Debug)] 96 | pub struct opa_number_ref_t { 97 | pub s: intptr_t, 98 | pub len: size_t, 99 | } 100 | 101 | #[repr(C)] 102 | #[derive(Copy, Clone)] 103 | pub union opa_number_variant_t { 104 | pub i: c_longlong, 105 | pub f: c_double, 106 | pub r: opa_number_ref_t, 107 | } 108 | 109 | #[repr(C)] 110 | #[derive(Copy, Clone)] 111 | pub struct opa_number_t { 112 | pub hdr: opa_value, 113 | pub repr: c_uchar, 114 | pub v: opa_number_variant_t, 115 | } 116 | 117 | impl opa_number_t { 118 | pub fn from_i64(i: i64) -> Self { 119 | let hdr = opa_value { ty: OPA_NUMBER }; 120 | let v = opa_number_variant_t { i }; 121 | opa_number_t { 122 | hdr, 123 | repr: OPA_NUMBER_REPR_INT, 124 | v, 125 | } 126 | } 127 | 128 | pub fn from_f64(f: f64) -> Self { 129 | let hdr = opa_value { ty: OPA_NUMBER }; 130 | let v = opa_number_variant_t { f }; 131 | opa_number_t { 132 | hdr, 133 | repr: OPA_NUMBER_REPR_FLOAT, 134 | v, 135 | } 136 | } 137 | 138 | pub fn from_str(s: &str, data: ValueAddr) -> Self { 139 | let hdr = opa_value { ty: OPA_NUMBER }; 140 | let len = s.len() as size_t; 141 | let r = opa_number_ref_t { 142 | s: data.0 as intptr_t, 143 | len, 144 | }; 145 | let v = opa_number_variant_t { r }; 146 | opa_number_t { 147 | hdr, 148 | repr: OPA_NUMBER_REPR_REF, 149 | v, 150 | } 151 | } 152 | } 153 | 154 | #[repr(C)] 155 | #[derive(Copy, Clone, Debug)] 156 | pub struct opa_string_t { 157 | pub hdr: opa_value, 158 | pub free: c_uchar, 159 | pub len: size_t, 160 | pub v: intptr_t, 161 | } 162 | 163 | impl opa_string_t { 164 | pub fn from_str(s: &str, data: ValueAddr) -> Self { 165 | let hdr = opa_value { ty: OPA_STRING }; 166 | let free = 0 as c_uchar; 167 | let len = s.len() as size_t; 168 | opa_string_t { 169 | hdr, 170 | free, 171 | len, 172 | v: data.0 as intptr_t, 173 | } 174 | } 175 | } 176 | 177 | #[repr(C)] 178 | #[derive(Copy, Clone, Debug)] 179 | pub struct opa_array_elem_t { 180 | pub i: intptr_t, 181 | pub v: intptr_t, 182 | } 183 | 184 | #[repr(C)] 185 | #[derive(Copy, Clone, Debug)] 186 | pub struct opa_array_t { 187 | pub hdr: opa_value, 188 | pub elems: intptr_t, 189 | pub len: size_t, 190 | pub cap: size_t, 191 | } 192 | 193 | impl opa_array_t { 194 | pub fn new(elems: ValueAddr, len: usize) -> Self { 195 | let hdr = opa_value { ty: OPA_ARRAY }; 196 | Self { 197 | hdr, 198 | elems: elems.0 as intptr_t, 199 | len: len as size_t, 200 | cap: 0 as size_t, 201 | } 202 | } 203 | } 204 | 205 | #[repr(C)] 206 | #[derive(Copy, Clone, Debug)] 207 | pub struct opa_object_elem_t { 208 | pub k: intptr_t, 209 | pub v: intptr_t, 210 | pub next: intptr_t, 211 | } 212 | 213 | #[repr(C)] 214 | #[derive(Copy, Clone, Debug)] 215 | pub struct opa_object_t { 216 | pub hdr: opa_value, 217 | pub head: intptr_t, 218 | } 219 | 220 | impl opa_object_t { 221 | pub fn new(head: ValueAddr) -> Self { 222 | let hdr = opa_value { ty: OPA_OBJECT }; 223 | Self { 224 | hdr, 225 | head: head.0 as i32, 226 | } 227 | } 228 | } 229 | 230 | #[repr(C)] 231 | #[derive(Copy, Clone, Debug)] 232 | pub struct opa_set_elem_t { 233 | pub v: intptr_t, 234 | pub next: intptr_t, 235 | } 236 | 237 | #[repr(C)] 238 | #[derive(Copy, Clone, Debug)] 239 | pub struct opa_set_t { 240 | pub hdr: opa_value, 241 | pub head: intptr_t, 242 | } 243 | 244 | impl opa_set_t { 245 | pub fn new(head: ValueAddr) -> Self { 246 | let hdr = opa_value { ty: OPA_SET }; 247 | Self { 248 | hdr, 249 | head: head.0 as i32, 250 | } 251 | } 252 | } 253 | 254 | #[cfg(test)] 255 | mod tests { 256 | use std::collections::HashMap; 257 | use std::fs; 258 | use std::mem; 259 | 260 | use proptest::prelude::*; 261 | use serde::{Deserialize, Serialize}; 262 | 263 | use crate::opa_serde::to_instance; 264 | use crate::runtime::{Instance, Memory, Module}; 265 | use crate::value::{self, Number, Value}; 266 | 267 | use super::*; 268 | 269 | thread_local! { 270 | static EMPTY_MODULE: Module = { 271 | let bytes = fs::read("tests/empty.wasm").unwrap(); 272 | let module = Module::from_bytes(bytes).unwrap(); 273 | module 274 | }; 275 | } 276 | 277 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 278 | struct UnitStruct; 279 | 280 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 281 | struct NewTypeStruct(i64); 282 | 283 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 284 | struct TupleStruct(i64, String); 285 | 286 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 287 | enum TestEnum { 288 | Unit, 289 | NewType(i64), 290 | Tuple(i64, String), 291 | Struct { age: i64, msg: String }, 292 | } 293 | 294 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 295 | struct Person { 296 | name: String, 297 | age: u8, 298 | properties: HashMap, 299 | } 300 | 301 | #[test] 302 | fn test_bool_size() { 303 | assert_eq!(8, mem::size_of::()); 304 | } 305 | 306 | #[test] 307 | fn test_number_ref_size() { 308 | assert_eq!(8, mem::size_of::()); 309 | } 310 | 311 | macro_rules! type_roundtrip { 312 | ($name:ident, $ty:ty, $input:expr) => { 313 | #[test] 314 | fn $name() { 315 | EMPTY_MODULE.with(|module| { 316 | let memory = Memory::from_module(module); 317 | let instance = Instance::new(module, memory).unwrap(); 318 | let addr = to_instance(&instance, &$input).unwrap(); 319 | let loaded = from_instance::<$ty>(&instance, addr).unwrap(); 320 | assert_eq!($input, loaded); 321 | }) 322 | } 323 | }; 324 | } 325 | 326 | type_roundtrip!(test_roundtrip_bool, bool, true); 327 | type_roundtrip!(test_roundtrip_i8, i8, 42_i8); 328 | type_roundtrip!(test_roundtrip_i16, i16, 42_i16); 329 | type_roundtrip!(test_roundtrip_i32, i32, 42_i32); 330 | type_roundtrip!(test_roundtrip_i64, i64, 42_i64); 331 | type_roundtrip!(test_roundtrip_u8, u8, 42_u8); 332 | type_roundtrip!(test_roundtrip_u16, u16, 42_u16); 333 | type_roundtrip!(test_roundtrip_u32, u32, 42_u32); 334 | type_roundtrip!(test_roundtrip_u64, u64, 42_u64); 335 | type_roundtrip!(test_roundtrip_f32, f32, 1.234_f32); 336 | type_roundtrip!(test_roundtrip_f64, f64, 1.234_f64); 337 | 338 | type_roundtrip!(test_roundtrip_string, String, "hello there".to_string()); 339 | type_roundtrip!(test_roundtrip_char, char, 'a'); 340 | type_roundtrip!(test_roundtrip_none, Option, Option::::None); 341 | type_roundtrip!(test_roundtrip_some, Option, Some(56)); 342 | type_roundtrip!(test_roundtrip_unit_struct, UnitStruct, UnitStruct); 343 | type_roundtrip!( 344 | test_roundtrip_newtype_struct, 345 | NewTypeStruct, 346 | NewTypeStruct(56) 347 | ); 348 | type_roundtrip!(test_roundtrip_unit_variant, TestEnum, TestEnum::Unit); 349 | type_roundtrip!( 350 | test_roundtrip_newtype_variant, 351 | TestEnum, 352 | TestEnum::NewType(64) 353 | ); 354 | type_roundtrip!( 355 | test_roundtrip_tuple_variant, 356 | TestEnum, 357 | TestEnum::Tuple(64, "Hello".to_string()) 358 | ); 359 | type_roundtrip!( 360 | test_roundtrip_struct_variant, 361 | TestEnum, 362 | TestEnum::Struct { 363 | age: 64, 364 | msg: "Hello".to_string() 365 | } 366 | ); 367 | 368 | type_roundtrip!( 369 | test_roundtrip_vec, 370 | Vec, 371 | vec!["hello".to_string(), "there".to_string()] 372 | ); 373 | type_roundtrip!( 374 | test_roundtrip_tuple, 375 | (i64, String), 376 | (42, "hello".to_string()) 377 | ); 378 | type_roundtrip!( 379 | test_roundtrip_tuple_struct, 380 | TupleStruct, 381 | TupleStruct(42, "hello".to_string()) 382 | ); 383 | 384 | #[test] 385 | fn test_roundtrip_map() { 386 | EMPTY_MODULE.with(|module| { 387 | let memory = Memory::from_module(module); 388 | let instance = Instance::new(module, memory).unwrap(); 389 | let mut input = HashMap::new(); 390 | input.insert("key1".to_string(), 3); 391 | input.insert("key2".to_string(), 2); 392 | let addr = to_instance(&instance, &input).unwrap(); 393 | let loaded = from_instance(&instance, addr).unwrap(); 394 | assert_eq!(input, loaded); 395 | }) 396 | } 397 | 398 | #[test] 399 | fn test_roundtrip_empty_map() { 400 | EMPTY_MODULE.with(|module| { 401 | let memory = Memory::from_module(module); 402 | let instance = Instance::new(module, memory).unwrap(); 403 | let input: HashMap = HashMap::new(); 404 | let addr = to_instance(&instance, &input).unwrap(); 405 | let loaded = from_instance(&instance, addr).unwrap(); 406 | assert_eq!(input, loaded); 407 | }) 408 | } 409 | 410 | #[test] 411 | fn test_roundtrip_struct() { 412 | EMPTY_MODULE.with(|module| { 413 | let memory = Memory::from_module(module); 414 | let instance = Instance::new(module, memory).unwrap(); 415 | let mut properties = HashMap::new(); 416 | properties.insert("height".to_string(), "50".to_string()); 417 | properties.insert("mood".to_string(), "happy".to_string()); 418 | let person = Person { 419 | name: "thename".to_string(), 420 | age: 42, 421 | properties, 422 | }; 423 | let addr = to_instance(&instance, &person).unwrap(); 424 | let loaded = from_instance(&instance, addr).unwrap(); 425 | assert_eq!(person, loaded); 426 | }) 427 | } 428 | 429 | #[test] 430 | fn test_roundtrip_unit() { 431 | EMPTY_MODULE.with(|module| { 432 | let memory = Memory::from_module(module); 433 | let instance = Instance::new(module, memory).unwrap(); 434 | let input = (); 435 | let addr = to_instance(&instance, &input).unwrap(); 436 | let loaded = from_instance(&instance, addr).unwrap(); 437 | assert_eq!(input, loaded); 438 | }) 439 | } 440 | 441 | // Value tests 442 | #[test] 443 | fn test_roundtrip_value_object() { 444 | EMPTY_MODULE.with(|module| { 445 | let memory = Memory::from_module(module); 446 | let instance = Instance::new(module, memory).unwrap(); 447 | let mut input = value::Map::new(); 448 | input.insert("key1".to_string(), Value::Number(3.into())); 449 | input.insert("key2".to_string(), Value::Bool(true)); 450 | let input = Value::Object(input); 451 | let addr = to_instance(&instance, &input).unwrap(); 452 | let loaded = from_instance(&instance, addr).unwrap(); 453 | assert_eq!(input, loaded); 454 | }) 455 | } 456 | 457 | #[test] 458 | fn test_roundtrip_value_set() { 459 | EMPTY_MODULE.with(|module| { 460 | let memory = Memory::from_module(module); 461 | let instance = Instance::new(module, memory).unwrap(); 462 | let mut input = value::Set::new(); 463 | input.insert(Value::String("key1".to_string())); 464 | input.insert(Value::String("key2".to_string())); 465 | let input = Value::Set(input); 466 | let addr = to_instance(&instance, &input).unwrap(); 467 | let loaded = from_instance(&instance, addr).unwrap(); 468 | assert_eq!(input, loaded); 469 | }) 470 | } 471 | 472 | fn arb_number() -> impl Strategy { 473 | prop_oneof![ 474 | prop::num::i64::ANY.prop_map(Number::from), 475 | prop::num::i64::ANY.prop_map(|i| Number::from(i.to_string())), 476 | prop::num::f64::ANY.prop_map(Number::from), 477 | prop::num::f64::ANY.prop_map(|f| Number::from(f.to_string())), 478 | ] 479 | } 480 | 481 | fn arb_value() -> impl Strategy { 482 | let leaf = prop_oneof![ 483 | Just(Value::Null), 484 | any::().prop_map(Value::Bool), 485 | arb_number().prop_map(Value::Number), 486 | ".*".prop_map(Value::String), 487 | ]; 488 | leaf.prop_recursive( 489 | 8, // 8 levels deep 490 | 256, // Shoot for maximum size of 256 nodes 491 | 10, // We put up to 10 items per collection 492 | |inner| { 493 | prop_oneof![ 494 | prop::collection::vec(inner.clone(), 0..10).prop_map(Value::Array), 495 | prop::collection::btree_map(".*", inner.clone(), 0..10).prop_map(Value::Object), 496 | prop::collection::btree_set(inner.clone(), 0..10).prop_map(Value::Set), 497 | ] 498 | }, 499 | ) 500 | } 501 | 502 | proptest! { 503 | #[test] 504 | fn test_roundtrip_value(input in arb_value()) { 505 | EMPTY_MODULE.with(|module| { 506 | let memory = Memory::from_module(module); 507 | let instance = Instance::new(module, memory).unwrap(); 508 | let addr = to_instance(&instance, &input).unwrap(); 509 | let loaded = from_instance(&instance, addr).unwrap(); 510 | assert_eq!(input, loaded); 511 | }) 512 | } 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /opa-wasm/src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use std::sync::Arc; 3 | 4 | use crate::{Error, ValueAddr}; 5 | 6 | #[cfg(target_arch = "x86_64")] 7 | mod wasmtime; 8 | 9 | #[cfg(not(target_arch = "x86_64"))] 10 | mod wasmi; 11 | 12 | #[cfg(target_arch = "x86_64")] 13 | pub use self::wasmtime::{Instance, Memory, Module}; 14 | 15 | #[cfg(not(target_arch = "x86_64"))] 16 | pub use self::wasmi::{Instance, Memory, Module}; 17 | 18 | #[cfg(target_arch = "x86_64")] 19 | use self::wasmtime::FunctionsImpl; 20 | 21 | #[cfg(not(target_arch = "x86_64"))] 22 | use self::wasmi::FunctionsImpl; 23 | 24 | pub trait AsBytes { 25 | fn as_bytes(&self) -> &[u8]; 26 | } 27 | 28 | impl AsBytes for [u8] { 29 | fn as_bytes(&self) -> &[u8] { 30 | &self 31 | } 32 | } 33 | 34 | impl<'a> AsBytes for &'a [u8] { 35 | fn as_bytes(&self) -> &[u8] { 36 | *self 37 | } 38 | } 39 | 40 | impl AsBytes for str { 41 | fn as_bytes(&self) -> &[u8] { 42 | self.as_bytes() 43 | } 44 | } 45 | 46 | impl<'a> AsBytes for &'a str { 47 | fn as_bytes(&self) -> &[u8] { 48 | str::as_bytes(&*self) 49 | } 50 | } 51 | 52 | pub unsafe trait FromBytes: Sized + Copy { 53 | fn len() -> usize { 54 | mem::size_of::() 55 | } 56 | 57 | fn from_bytes(bytes: &[u8]) -> Result { 58 | if bytes.len() < mem::size_of::() { 59 | return Err(Error::NotEnoughData(mem::size_of::(), bytes.len())); 60 | } 61 | 62 | let bytes_ptr = bytes.as_ptr(); 63 | let struct_ptr = bytes_ptr as *const Self; 64 | let struct_ref = unsafe { *struct_ptr }; 65 | Ok(struct_ref) 66 | } 67 | } 68 | 69 | #[derive(Clone, Debug)] 70 | pub struct Functions { 71 | inner: Arc, 72 | } 73 | 74 | impl Functions { 75 | pub fn from_impl(inner: FunctionsImpl) -> Result { 76 | let f = Self { 77 | inner: Arc::new(inner), 78 | }; 79 | Ok(f) 80 | } 81 | 82 | pub fn builtins(&self) -> Result { 83 | let addr = self.inner.builtins()?; 84 | Ok(addr.into()) 85 | } 86 | 87 | pub fn eval_ctx_new(&self) -> Result { 88 | let addr = self.inner.opa_eval_ctx_new()?; 89 | Ok(addr.into()) 90 | } 91 | 92 | pub fn eval_ctx_set_input(&self, ctx: ValueAddr, input: ValueAddr) -> Result<(), Error> { 93 | self.inner.opa_eval_ctx_set_input(ctx.0, input.0)?; 94 | Ok(()) 95 | } 96 | 97 | pub fn eval_ctx_set_data(&self, ctx: ValueAddr, data: ValueAddr) -> Result<(), Error> { 98 | self.inner.opa_eval_ctx_set_data(ctx.0, data.0)?; 99 | Ok(()) 100 | } 101 | 102 | pub fn eval(&self, ctx: ValueAddr) -> Result<(), Error> { 103 | self.inner.eval(ctx.0)?; 104 | Ok(()) 105 | } 106 | 107 | pub fn eval_ctx_get_result(&self, ctx: ValueAddr) -> Result { 108 | let addr = self.inner.opa_eval_ctx_get_result(ctx.0)?; 109 | Ok(addr.into()) 110 | } 111 | 112 | pub fn heap_ptr_get(&self) -> Result { 113 | let addr = self.inner.opa_heap_ptr_get()?; 114 | Ok(addr.into()) 115 | } 116 | 117 | pub fn heap_ptr_set(&self, addr: ValueAddr) -> Result<(), Error> { 118 | self.inner.opa_heap_ptr_set(addr.0)?; 119 | Ok(()) 120 | } 121 | 122 | pub fn heap_top_get(&self) -> Result { 123 | let addr = self.inner.opa_heap_top_get()?; 124 | Ok(addr.into()) 125 | } 126 | 127 | pub fn heap_top_set(&self, addr: ValueAddr) -> Result<(), Error> { 128 | self.inner.opa_heap_top_set(addr.0)?; 129 | Ok(()) 130 | } 131 | 132 | pub fn malloc(&self, len: usize) -> Result { 133 | let addr = self.inner.opa_malloc(len as i32)?; 134 | Ok(addr.into()) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /opa-wasm/src/runtime/wasmi.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::Path; 3 | 4 | use wasmi::memory_units::Pages; 5 | use wasmi::{ 6 | Externals, FuncInstance, FuncRef, ImportsBuilder, MemoryDescriptor, MemoryInstance, MemoryRef, 7 | ModuleImportResolver, RuntimeArgs, RuntimeValue, Signature, Trap, TrapKind, ValueType, 8 | }; 9 | 10 | use crate::builtins::Builtins; 11 | use crate::error::Error; 12 | use crate::ValueAddr; 13 | 14 | use super::{AsBytes, FromBytes, Functions}; 15 | 16 | const ABORT_FUNC_INDEX: usize = 1; 17 | const BUILTIN0_FUNC_INDEX: usize = 2; 18 | const BUILTIN1_FUNC_INDEX: usize = 3; 19 | const BUILTIN2_FUNC_INDEX: usize = 4; 20 | const BUILTIN3_FUNC_INDEX: usize = 5; 21 | const BUILTIN4_FUNC_INDEX: usize = 6; 22 | 23 | #[derive(Clone, Debug)] 24 | struct HostExternals { 25 | memory: Memory, 26 | builtins: Builtins, 27 | } 28 | 29 | impl HostExternals { 30 | fn check_signature(&self, index: usize, signature: &Signature) -> bool { 31 | let (params, ret_ty): (&[ValueType], Option) = match index { 32 | ABORT_FUNC_INDEX => (&[ValueType::I32], None), 33 | BUILTIN0_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)), 34 | BUILTIN1_FUNC_INDEX => ( 35 | &[ValueType::I32, ValueType::I32, ValueType::I32], 36 | Some(ValueType::I32), 37 | ), 38 | BUILTIN2_FUNC_INDEX => ( 39 | &[ 40 | ValueType::I32, 41 | ValueType::I32, 42 | ValueType::I32, 43 | ValueType::I32, 44 | ], 45 | Some(ValueType::I32), 46 | ), 47 | BUILTIN3_FUNC_INDEX => ( 48 | &[ 49 | ValueType::I32, 50 | ValueType::I32, 51 | ValueType::I32, 52 | ValueType::I32, 53 | ValueType::I32, 54 | ], 55 | Some(ValueType::I32), 56 | ), 57 | BUILTIN4_FUNC_INDEX => ( 58 | &[ 59 | ValueType::I32, 60 | ValueType::I32, 61 | ValueType::I32, 62 | ValueType::I32, 63 | ValueType::I32, 64 | ValueType::I32, 65 | ], 66 | Some(ValueType::I32), 67 | ), 68 | _ => return false, 69 | }; 70 | signature.params() == params && signature.return_type() == ret_ty 71 | } 72 | } 73 | 74 | impl ModuleImportResolver for HostExternals { 75 | fn resolve_memory( 76 | &self, 77 | _field_name: &str, 78 | _descriptor: &MemoryDescriptor, 79 | ) -> Result { 80 | Ok(self.memory.0.clone()) 81 | } 82 | 83 | fn resolve_func( 84 | &self, 85 | field_name: &str, 86 | signature: &Signature, 87 | ) -> Result { 88 | let index = match field_name { 89 | "opa_abort" => ABORT_FUNC_INDEX, 90 | "opa_builtin0" => BUILTIN0_FUNC_INDEX, 91 | "opa_builtin1" => BUILTIN1_FUNC_INDEX, 92 | "opa_builtin2" => BUILTIN2_FUNC_INDEX, 93 | "opa_builtin3" => BUILTIN3_FUNC_INDEX, 94 | "opa_builtin4" => BUILTIN4_FUNC_INDEX, 95 | _ => { 96 | return Err(wasmi::Error::Instantiation(format!( 97 | "Export {} not found", 98 | field_name 99 | ))) 100 | } 101 | }; 102 | 103 | if !self.check_signature(index, signature) { 104 | return Err(wasmi::Error::Instantiation(format!( 105 | "Export {} has a bad signature", 106 | field_name 107 | ))); 108 | } 109 | 110 | let f = match field_name { 111 | "opa_abort" => { 112 | FuncInstance::alloc_host(Signature::new(&[ValueType::I32][..], None), index) 113 | } 114 | "opa_builtin0" => FuncInstance::alloc_host( 115 | Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), 116 | index, 117 | ), 118 | "opa_builtin1" => FuncInstance::alloc_host( 119 | Signature::new( 120 | &[ValueType::I32, ValueType::I32, ValueType::I32][..], 121 | Some(ValueType::I32), 122 | ), 123 | index, 124 | ), 125 | "opa_builtin2" => FuncInstance::alloc_host( 126 | Signature::new( 127 | &[ 128 | ValueType::I32, 129 | ValueType::I32, 130 | ValueType::I32, 131 | ValueType::I32, 132 | ][..], 133 | Some(ValueType::I32), 134 | ), 135 | index, 136 | ), 137 | "opa_builtin3" => FuncInstance::alloc_host( 138 | Signature::new( 139 | &[ 140 | ValueType::I32, 141 | ValueType::I32, 142 | ValueType::I32, 143 | ValueType::I32, 144 | ValueType::I32, 145 | ][..], 146 | Some(ValueType::I32), 147 | ), 148 | index, 149 | ), 150 | "opa_builtin4" => FuncInstance::alloc_host( 151 | Signature::new( 152 | &[ 153 | ValueType::I32, 154 | ValueType::I32, 155 | ValueType::I32, 156 | ValueType::I32, 157 | ValueType::I32, 158 | ValueType::I32, 159 | ][..], 160 | Some(ValueType::I32), 161 | ), 162 | index, 163 | ), 164 | _ => unreachable!(), 165 | }; 166 | Ok(f) 167 | } 168 | } 169 | 170 | impl Externals for HostExternals { 171 | fn invoke_index( 172 | &mut self, 173 | index: usize, 174 | args: RuntimeArgs, 175 | ) -> Result, Trap> { 176 | let result = match index { 177 | ABORT_FUNC_INDEX => { 178 | let addr = args.nth_checked(0)?; 179 | crate::abort(addr); 180 | None 181 | } 182 | BUILTIN0_FUNC_INDEX => { 183 | let id = args.nth_checked(0)?; 184 | let ctx: i32 = args.nth_checked(1)?; 185 | let result = self.builtins.builtin0(id, ctx.into()); 186 | Some(RuntimeValue::I32(result.into())) 187 | } 188 | BUILTIN1_FUNC_INDEX => { 189 | let id = args.nth_checked(0)?; 190 | let ctx: i32 = args.nth_checked(1)?; 191 | let arg0: i32 = args.nth_checked(2)?; 192 | let result = self.builtins.builtin1(id, ctx.into(), arg0.into()); 193 | Some(RuntimeValue::I32(result.into())) 194 | } 195 | BUILTIN2_FUNC_INDEX => { 196 | let id = args.nth_checked(0)?; 197 | let ctx: i32 = args.nth_checked(1)?; 198 | let arg0: i32 = args.nth_checked(2)?; 199 | let arg1: i32 = args.nth_checked(3)?; 200 | let result = self 201 | .builtins 202 | .builtin2(id, ctx.into(), arg0.into(), arg1.into()); 203 | Some(RuntimeValue::I32(result.into())) 204 | } 205 | BUILTIN3_FUNC_INDEX => { 206 | let id = args.nth_checked(0)?; 207 | let ctx: i32 = args.nth_checked(1)?; 208 | let arg0: i32 = args.nth_checked(2)?; 209 | let arg1: i32 = args.nth_checked(3)?; 210 | let arg2: i32 = args.nth_checked(4)?; 211 | let result = 212 | self.builtins 213 | .builtin3(id, ctx.into(), arg0.into(), arg1.into(), arg2.into()); 214 | Some(RuntimeValue::I32(result.into())) 215 | } 216 | BUILTIN4_FUNC_INDEX => { 217 | let id = args.nth_checked(0)?; 218 | let ctx: i32 = args.nth_checked(1)?; 219 | let arg0: i32 = args.nth_checked(2)?; 220 | let arg1: i32 = args.nth_checked(3)?; 221 | let arg2: i32 = args.nth_checked(4)?; 222 | let arg3: i32 = args.nth_checked(5)?; 223 | let result = self.builtins.builtin4( 224 | id, 225 | ctx.into(), 226 | arg0.into(), 227 | arg1.into(), 228 | arg2.into(), 229 | arg3.into(), 230 | ); 231 | Some(RuntimeValue::I32(result.into())) 232 | } 233 | _ => return Err(TrapKind::ElemUninitialized.into()), 234 | }; 235 | Ok(result) 236 | } 237 | } 238 | 239 | #[derive(Clone, Debug)] 240 | pub struct Instance { 241 | memory: Memory, 242 | functions: Functions, 243 | externals: HostExternals, 244 | } 245 | 246 | impl Instance { 247 | pub fn new(module: &Module, memory: Memory) -> Result { 248 | let builtins = Builtins::default(); 249 | let externals = HostExternals { 250 | memory: memory.clone(), 251 | builtins: builtins.clone(), 252 | }; 253 | let imports = ImportsBuilder::new().with_resolver("env", &externals); 254 | let instance = wasmi::ModuleInstance::new(&module.0, &imports) 255 | .map_err(Error::Wasmi)? 256 | .assert_no_start(); 257 | let fimpl = FunctionsImpl::new(instance, externals.clone())?; 258 | let functions = Functions::from_impl(fimpl)?; 259 | let instance = Instance { 260 | memory, 261 | functions, 262 | externals, 263 | }; 264 | builtins.replace(instance.clone())?; 265 | 266 | Ok(instance) 267 | } 268 | 269 | pub fn functions(&self) -> &Functions { 270 | &self.functions 271 | } 272 | 273 | pub fn memory(&self) -> &Memory { 274 | &self.memory 275 | } 276 | } 277 | 278 | #[derive(Clone, Debug)] 279 | pub struct Memory(MemoryRef); 280 | 281 | impl Memory { 282 | pub fn from_module(_module: &Module) -> Self { 283 | let memory = MemoryInstance::alloc(Pages(5), None).unwrap(); 284 | Memory(memory) 285 | } 286 | 287 | pub fn get(&self, addr: ValueAddr) -> Result { 288 | let start = addr.0 as usize; 289 | let t = self 290 | .0 291 | .with_direct_access(|bytes| T::from_bytes(&bytes[start..]))?; 292 | Ok(t) 293 | } 294 | 295 | pub fn get_bytes(&self, addr: ValueAddr, len: usize) -> Result, Error> { 296 | let start = addr.0 as u32; 297 | self.0.get(start, len).map_err(Error::Wasmi) 298 | } 299 | 300 | pub fn set(&self, addr: ValueAddr, value: &T) -> Result<(), Error> { 301 | self.0 302 | .set(addr.0 as u32, value.as_bytes()) 303 | .map_err(Error::Wasmi) 304 | } 305 | } 306 | 307 | pub struct Module(wasmi::Module); 308 | 309 | impl Module { 310 | pub fn from_file>(path: P) -> Result { 311 | let bytes = fs::read(path).map_err(Error::FileRead)?; 312 | Self::from_bytes(bytes) 313 | } 314 | 315 | pub fn from_bytes>(bytes: B) -> Result { 316 | let module = wasmi::Module::from_buffer(&bytes).map_err(Error::Wasmi)?; 317 | Ok(Module(module)) 318 | } 319 | } 320 | 321 | #[derive(Debug)] 322 | pub struct FunctionsImpl { 323 | module_ref: wasmi::ModuleRef, 324 | externals: HostExternals, 325 | } 326 | 327 | impl FunctionsImpl { 328 | fn new(module_ref: wasmi::ModuleRef, externals: HostExternals) -> Result { 329 | let f = FunctionsImpl { 330 | module_ref, 331 | externals, 332 | }; 333 | Ok(f) 334 | } 335 | 336 | pub fn builtins(&self) -> Result { 337 | let args = []; 338 | let mut externals = self.externals.clone(); 339 | self.module_ref 340 | .invoke_export("builtins", &args[..], &mut externals) 341 | .map(|v| v.and_then(|r| r.try_into::())) 342 | .map_err(Error::Wasmi) 343 | .transpose() 344 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 345 | } 346 | 347 | pub fn opa_eval_ctx_new(&self) -> Result { 348 | let args = []; 349 | let mut externals = self.externals.clone(); 350 | self.module_ref 351 | .invoke_export("opa_eval_ctx_new", &args[..], &mut externals) 352 | .map(|v| v.and_then(|r| r.try_into::())) 353 | .map_err(Error::Wasmi) 354 | .transpose() 355 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 356 | } 357 | 358 | pub fn opa_eval_ctx_set_input(&self, ctx: i32, input: i32) -> Result<(), Error> { 359 | let args = [RuntimeValue::I32(ctx), RuntimeValue::I32(input)]; 360 | let mut externals = self.externals.clone(); 361 | self.module_ref 362 | .invoke_export("opa_eval_ctx_set_input", &args[..], &mut externals) 363 | .map(drop) 364 | .map_err(Error::Wasmi) 365 | } 366 | 367 | pub fn opa_eval_ctx_set_data(&self, ctx: i32, data: i32) -> Result<(), Error> { 368 | let args = [RuntimeValue::I32(ctx), RuntimeValue::I32(data)]; 369 | let mut externals = self.externals.clone(); 370 | self.module_ref 371 | .invoke_export("opa_eval_ctx_set_data", &args[..], &mut externals) 372 | .map(drop) 373 | .map_err(Error::Wasmi) 374 | } 375 | 376 | pub fn eval(&self, ctx: i32) -> Result<(), Error> { 377 | let args = [RuntimeValue::I32(ctx)]; 378 | let mut externals = self.externals.clone(); 379 | self.module_ref 380 | .invoke_export("eval", &args[..], &mut externals) 381 | .map(drop) 382 | .map_err(Error::Wasmi) 383 | } 384 | 385 | pub fn opa_eval_ctx_get_result(&self, ctx: i32) -> Result { 386 | let args = [RuntimeValue::I32(ctx)]; 387 | let mut externals = self.externals.clone(); 388 | self.module_ref 389 | .invoke_export("opa_eval_ctx_get_result", &args[..], &mut externals) 390 | .map(|v| v.and_then(|r| r.try_into::())) 391 | .map_err(Error::Wasmi) 392 | .transpose() 393 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 394 | } 395 | 396 | pub fn opa_heap_ptr_get(&self) -> Result { 397 | let args = []; 398 | let mut externals = self.externals.clone(); 399 | self.module_ref 400 | .invoke_export("opa_heap_ptr_get", &args[..], &mut externals) 401 | .map(|v| v.and_then(|r| r.try_into::())) 402 | .map_err(Error::Wasmi) 403 | .transpose() 404 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 405 | } 406 | 407 | pub fn opa_heap_ptr_set(&self, addr: i32) -> Result<(), Error> { 408 | let args = [RuntimeValue::I32(addr)]; 409 | let mut externals = self.externals.clone(); 410 | self.module_ref 411 | .invoke_export("opa_heap_ptr_set", &args[..], &mut externals) 412 | .map(drop) 413 | .map_err(Error::Wasmi) 414 | } 415 | 416 | pub fn opa_heap_top_get(&self) -> Result { 417 | let args = []; 418 | let mut externals = self.externals.clone(); 419 | self.module_ref 420 | .invoke_export("opa_heap_top_get", &args[..], &mut externals) 421 | .map(|v| v.and_then(|r| r.try_into::())) 422 | .map_err(Error::Wasmi) 423 | .transpose() 424 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 425 | } 426 | 427 | pub fn opa_heap_top_set(&self, addr: i32) -> Result<(), Error> { 428 | let args = [RuntimeValue::I32(addr)]; 429 | let mut externals = self.externals.clone(); 430 | self.module_ref 431 | .invoke_export("opa_heap_top_set", &args[..], &mut externals) 432 | .map(drop) 433 | .map_err(Error::Wasmi) 434 | } 435 | 436 | pub fn opa_malloc(&self, len: i32) -> Result { 437 | let args = [RuntimeValue::I32(len)]; 438 | let mut externals = self.externals.clone(); 439 | self.module_ref 440 | .invoke_export("opa_malloc", &args[..], &mut externals) 441 | .map(|v| v.and_then(|r| r.try_into::())) 442 | .map_err(Error::Wasmi) 443 | .transpose() 444 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 445 | } 446 | 447 | pub fn opa_json_parse(&self, addr: i32, len: i32) -> Result { 448 | let args = [RuntimeValue::I32(addr), RuntimeValue::I32(len)]; 449 | let mut externals = self.externals.clone(); 450 | self.module_ref 451 | .invoke_export("opa_json_parse", &args[..], &mut externals) 452 | .map(|v| v.and_then(|r| r.try_into::())) 453 | .map_err(Error::Wasmi) 454 | .transpose() 455 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 456 | } 457 | 458 | pub fn opa_json_dump(&self, addr: i32) -> Result { 459 | let args = [RuntimeValue::I32(addr)]; 460 | let mut externals = self.externals.clone(); 461 | self.module_ref 462 | .invoke_export("opa_json_dump", &args[..], &mut externals) 463 | .map(|v| v.and_then(|r| r.try_into::())) 464 | .map_err(Error::Wasmi) 465 | .transpose() 466 | .unwrap_or_else(|| Err(Error::InvalidResult("i32"))) 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /opa-wasm/src/runtime/wasmtime.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::path::Path; 3 | 4 | use wasmtime::{Extern, Func, Limits, MemoryType, Store, Trap}; 5 | 6 | use crate::builtins::Builtins; 7 | use crate::error::Error; 8 | use crate::ValueAddr; 9 | 10 | use super::{AsBytes, FromBytes, Functions}; 11 | 12 | #[derive(Clone)] 13 | pub struct Instance { 14 | memory: Memory, 15 | functions: Functions, 16 | } 17 | 18 | impl Instance { 19 | pub fn new(module: &Module, memory: Memory) -> Result { 20 | // Builtins are tricky to handle. 21 | // We need to setup the functions as imports before creating 22 | // the instance. However, these functions require an instance to be called. 23 | // This is a circular dependency, which needless to say poses problems for 24 | // rust. 25 | // 26 | // To workaround this, we create an empty Builtins struct that we pass to the 27 | // imports so they can get a reference. Then, the instance is created and the 28 | // Builtins struct is updated with the instance. This is safe because none of 29 | // the builtins are called before the instance is created. It makes the Builtins 30 | // struct annoyingly complex because we need to use an Arc for shared references 31 | // as well as mutate the contents, requiring a RefCell. 32 | let builtins = Builtins::default(); 33 | 34 | let b0 = builtins.clone(); 35 | let b1 = builtins.clone(); 36 | let b2 = builtins.clone(); 37 | let b3 = builtins.clone(); 38 | let b4 = builtins.clone(); 39 | 40 | let imports = [ 41 | Extern::Memory(memory.clone().0), 42 | Extern::Func(Func::wrap1(module.0.store(), crate::abort)), 43 | Extern::Func(Func::wrap2(module.0.store(), move |id, ctx| { 44 | i32::from(b0.builtin0(id, ValueAddr(ctx))) 45 | })), 46 | Extern::Func(Func::wrap3(module.0.store(), move |id, ctx, a| { 47 | i32::from(b1.builtin1(id, ValueAddr(ctx), ValueAddr(a))) 48 | })), 49 | Extern::Func(Func::wrap4(module.0.store(), move |id, ctx, a, b| { 50 | i32::from(b2.builtin2(id, ValueAddr(ctx), ValueAddr(a), ValueAddr(b))) 51 | })), 52 | Extern::Func(Func::wrap5(module.0.store(), move |id, ctx, a, b, c| { 53 | i32::from(b3.builtin3(id, ValueAddr(ctx), ValueAddr(a), ValueAddr(b), ValueAddr(c))) 54 | })), 55 | Extern::Func(Func::wrap6(module.0.store(), move |id, ctx, a, b, c, d| { 56 | i32::from(b4.builtin4( 57 | id, 58 | ValueAddr(ctx), 59 | ValueAddr(a), 60 | ValueAddr(b), 61 | ValueAddr(c), 62 | ValueAddr(d), 63 | )) 64 | })), 65 | ]; 66 | 67 | let instance = 68 | wasmtime::Instance::new(&module.0, &imports).map_err(|e| Error::Wasmtime(e))?; 69 | let fimpl = FunctionsImpl::from_instance(instance)?; 70 | let functions = Functions::from_impl(fimpl)?; 71 | 72 | let instance = Instance { memory, functions }; 73 | builtins.replace(instance.clone())?; 74 | 75 | Ok(instance) 76 | } 77 | 78 | pub fn functions(&self) -> &Functions { 79 | &self.functions 80 | } 81 | 82 | pub fn memory(&self) -> &Memory { 83 | &self.memory 84 | } 85 | } 86 | 87 | impl fmt::Debug for Instance { 88 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 89 | write!(formatter, "Instance") 90 | } 91 | } 92 | 93 | #[derive(Clone)] 94 | pub struct Memory(wasmtime::Memory); 95 | 96 | impl Memory { 97 | pub fn from_module(module: &Module) -> Self { 98 | let memorytype = MemoryType::new(Limits::new(5, None)); 99 | let memory = wasmtime::Memory::new(module.0.store(), memorytype); 100 | Memory(memory) 101 | } 102 | 103 | pub fn get(&self, addr: ValueAddr) -> Result { 104 | let start = addr.0 as usize; 105 | let t = unsafe { T::from_bytes(&self.0.data_unchecked()[start..])? }; 106 | Ok(t) 107 | } 108 | 109 | pub fn get_bytes(&self, addr: ValueAddr, len: usize) -> Result, Error> { 110 | let start = addr.0 as usize; 111 | let end = start + len; 112 | let t = unsafe { Vec::from(&self.0.data_unchecked()[start..end]) }; 113 | Ok(t) 114 | } 115 | 116 | pub fn set(&self, addr: ValueAddr, value: &T) -> Result<(), Error> { 117 | let bytes = value.as_bytes(); 118 | unsafe { 119 | let start = addr.0 as usize; 120 | let end = start + bytes.len(); 121 | self.0.data_unchecked_mut()[start..end].copy_from_slice(bytes); 122 | } 123 | Ok(()) 124 | } 125 | } 126 | 127 | impl fmt::Debug for Memory { 128 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 129 | write!(formatter, "Memory") 130 | } 131 | } 132 | 133 | #[derive(Clone)] 134 | pub struct Module(wasmtime::Module); 135 | 136 | impl Module { 137 | pub fn from_file>(path: P) -> Result { 138 | let store = Store::default(); 139 | let module = wasmtime::Module::from_file(&store, &path).map_err(Error::Wasmtime)?; 140 | Ok(Module(module)) 141 | } 142 | 143 | pub fn from_bytes>(bytes: B) -> Result { 144 | let store = Store::default(); 145 | let module = wasmtime::Module::new(&store, bytes).map_err(Error::Wasmtime)?; 146 | Ok(Module(module)) 147 | } 148 | } 149 | 150 | #[allow(dead_code)] 151 | pub struct FunctionsImpl { 152 | instance: wasmtime::Instance, 153 | opa_malloc: Box Result>, 154 | opa_json_parse: Box Result>, 155 | opa_json_dump: Box Result>, 156 | opa_heap_ptr_get: Box Result>, 157 | opa_heap_ptr_set: Box Result<(), Trap>>, 158 | opa_heap_top_get: Box Result>, 159 | opa_heap_top_set: Box Result<(), Trap>>, 160 | opa_eval_ctx_new: Box Result>, 161 | opa_eval_ctx_set_input: Box Result<(), Trap>>, 162 | opa_eval_ctx_set_data: Box Result<(), Trap>>, 163 | opa_eval_ctx_get_result: Box Result>, 164 | builtins: Box Result>, 165 | eval: Box Result>, 166 | } 167 | 168 | impl FunctionsImpl { 169 | fn from_instance(instance: wasmtime::Instance) -> Result { 170 | let opa_malloc = instance 171 | .get_export("opa_malloc") 172 | .and_then(|ext| ext.func()) 173 | .ok_or_else(|| Error::MissingExport("opa_malloc")) 174 | .and_then(|f| f.get1::().map_err(|e| Error::Wasmtime(e)))?; 175 | 176 | let opa_json_parse = instance 177 | .get_export("opa_json_parse") 178 | .and_then(|ext| ext.func()) 179 | .ok_or_else(|| Error::MissingExport("opa_json_parse")) 180 | .and_then(|f| f.get2::().map_err(|e| Error::Wasmtime(e)))?; 181 | 182 | let opa_json_dump = instance 183 | .get_export("opa_json_dump") 184 | .and_then(|ext| ext.func()) 185 | .ok_or_else(|| Error::MissingExport("opa_json_dump")) 186 | .and_then(|f| f.get1::().map_err(|e| Error::Wasmtime(e)))?; 187 | 188 | let opa_heap_ptr_get = instance 189 | .get_export("opa_heap_ptr_get") 190 | .and_then(|ext| ext.func()) 191 | .ok_or_else(|| Error::MissingExport("opa_heap_ptr_get")) 192 | .and_then(|f| f.get0::().map_err(|e| Error::Wasmtime(e)))?; 193 | 194 | let opa_heap_ptr_set = instance 195 | .get_export("opa_heap_ptr_set") 196 | .and_then(|ext| ext.func()) 197 | .ok_or_else(|| Error::MissingExport("opa_heap_ptr_set")) 198 | .and_then(|f| f.get1::().map_err(|e| Error::Wasmtime(e)))?; 199 | 200 | let opa_heap_top_get = instance 201 | .get_export("opa_heap_top_get") 202 | .and_then(|ext| ext.func()) 203 | .ok_or_else(|| Error::MissingExport("opa_heap_top_get")) 204 | .and_then(|f| f.get0::().map_err(|e| Error::Wasmtime(e)))?; 205 | 206 | let opa_heap_top_set = instance 207 | .get_export("opa_heap_top_set") 208 | .and_then(|ext| ext.func()) 209 | .ok_or_else(|| Error::MissingExport("opa_heap_top_set")) 210 | .and_then(|f| f.get1::().map_err(|e| Error::Wasmtime(e)))?; 211 | 212 | let opa_eval_ctx_new = instance 213 | .get_export("opa_eval_ctx_new") 214 | .and_then(|ext| ext.func()) 215 | .ok_or_else(|| Error::MissingExport("opa_eval_ctx_new")) 216 | .and_then(|f| f.get0::().map_err(|e| Error::Wasmtime(e)))?; 217 | 218 | let opa_eval_ctx_set_input = instance 219 | .get_export("opa_eval_ctx_set_input") 220 | .and_then(|ext| ext.func()) 221 | .ok_or_else(|| Error::MissingExport("opa_eval_ctx_set_input")) 222 | .and_then(|f| f.get2::().map_err(|e| Error::Wasmtime(e)))?; 223 | 224 | let opa_eval_ctx_set_data = instance 225 | .get_export("opa_eval_ctx_set_data") 226 | .and_then(|ext| ext.func()) 227 | .ok_or_else(|| Error::MissingExport("opa_eval_ctx_set_data")) 228 | .and_then(|f| f.get2::().map_err(|e| Error::Wasmtime(e)))?; 229 | 230 | let opa_eval_ctx_get_result = instance 231 | .get_export("opa_eval_ctx_get_result") 232 | .and_then(|ext| ext.func()) 233 | .ok_or_else(|| Error::MissingExport("opa_eval_ctx_get_result")) 234 | .and_then(|f| f.get1::().map_err(|e| Error::Wasmtime(e)))?; 235 | 236 | let builtins = instance 237 | .get_export("builtins") 238 | .and_then(|ext| ext.func()) 239 | .ok_or_else(|| Error::MissingExport("builtins")) 240 | .and_then(|f| f.get0::().map_err(|e| Error::Wasmtime(e)))?; 241 | 242 | let eval = instance 243 | .get_export("eval") 244 | .and_then(|ext| ext.func()) 245 | .ok_or_else(|| Error::MissingExport("eval")) 246 | .and_then(|f| f.get1::().map_err(|e| Error::Wasmtime(e)))?; 247 | 248 | let inner = FunctionsImpl { 249 | instance, 250 | opa_malloc: Box::new(opa_malloc), 251 | opa_json_parse: Box::new(opa_json_parse), 252 | opa_json_dump: Box::new(opa_json_dump), 253 | opa_heap_ptr_get: Box::new(opa_heap_ptr_get), 254 | opa_heap_ptr_set: Box::new(opa_heap_ptr_set), 255 | opa_heap_top_get: Box::new(opa_heap_top_get), 256 | opa_heap_top_set: Box::new(opa_heap_top_set), 257 | opa_eval_ctx_new: Box::new(opa_eval_ctx_new), 258 | opa_eval_ctx_set_input: Box::new(opa_eval_ctx_set_input), 259 | opa_eval_ctx_set_data: Box::new(opa_eval_ctx_set_data), 260 | opa_eval_ctx_get_result: Box::new(opa_eval_ctx_get_result), 261 | builtins: Box::new(builtins), 262 | eval: Box::new(eval), 263 | }; 264 | Ok(inner) 265 | } 266 | 267 | pub fn builtins(&self) -> Result { 268 | let addr = (self.builtins)().map_err(Error::Trap)?; 269 | Ok(addr) 270 | } 271 | 272 | pub fn opa_eval_ctx_new(&self) -> Result { 273 | let addr = (self.opa_eval_ctx_new)().map_err(Error::Trap)?; 274 | Ok(addr) 275 | } 276 | 277 | pub fn opa_eval_ctx_set_input(&self, ctx: i32, input: i32) -> Result<(), Error> { 278 | (self.opa_eval_ctx_set_input)(ctx, input).map_err(Error::Trap)?; 279 | Ok(()) 280 | } 281 | 282 | pub fn opa_eval_ctx_set_data(&self, ctx: i32, data: i32) -> Result<(), Error> { 283 | (self.opa_eval_ctx_set_data)(ctx, data).map_err(Error::Trap)?; 284 | Ok(()) 285 | } 286 | 287 | pub fn eval(&self, ctx: i32) -> Result<(), Error> { 288 | (self.eval)(ctx).map_err(Error::Trap)?; 289 | Ok(()) 290 | } 291 | 292 | pub fn opa_eval_ctx_get_result(&self, ctx: i32) -> Result { 293 | let addr = (self.opa_eval_ctx_get_result)(ctx).map_err(Error::Trap)?; 294 | Ok(addr) 295 | } 296 | 297 | pub fn opa_heap_ptr_get(&self) -> Result { 298 | let addr = (self.opa_heap_ptr_get)().map_err(Error::Trap)?; 299 | Ok(addr) 300 | } 301 | 302 | pub fn opa_heap_ptr_set(&self, addr: i32) -> Result<(), Error> { 303 | (self.opa_heap_ptr_set)(addr).map_err(Error::Trap)?; 304 | Ok(()) 305 | } 306 | 307 | pub fn opa_heap_top_get(&self) -> Result { 308 | let addr = (self.opa_heap_top_get)().map_err(Error::Trap)?; 309 | Ok(addr) 310 | } 311 | 312 | pub fn opa_heap_top_set(&self, addr: i32) -> Result<(), Error> { 313 | (self.opa_heap_top_set)(addr).map_err(Error::Trap)?; 314 | Ok(()) 315 | } 316 | 317 | pub fn opa_malloc(&self, len: i32) -> Result { 318 | let addr = (self.opa_malloc)(len).map_err(Error::Trap)?; 319 | Ok(addr) 320 | } 321 | 322 | pub fn opa_json_parse(&self, addr: i32, len: i32) -> Result { 323 | let parsed_addr = (self.opa_json_parse)(addr, len)?; 324 | Ok(parsed_addr) 325 | } 326 | 327 | pub fn opa_json_dump(&self, addr: i32) -> Result { 328 | let raw_addr = (self.opa_json_dump)(addr).map_err(Error::Trap)?; 329 | Ok(raw_addr) 330 | } 331 | } 332 | 333 | impl fmt::Debug for FunctionsImpl { 334 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 335 | write!(formatter, "FunctionsImpl") 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /opa-wasm/src/set.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::marker::PhantomData; 3 | 4 | use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; 5 | 6 | pub(crate) const TOKEN: &str = "$policy::opa::private::set"; 7 | 8 | pub fn serialize(t: &T, serializer: S) -> Result 9 | where 10 | S: Serializer, 11 | T: Serialize, 12 | { 13 | use serde::ser::SerializeStruct; 14 | 15 | let mut s = serializer.serialize_struct(TOKEN, 1)?; 16 | s.serialize_field(TOKEN, t)?; 17 | s.end() 18 | } 19 | 20 | pub fn deserialize<'de, D, T>(deserializer: D) -> Result 21 | where 22 | D: Deserializer<'de>, 23 | T: Deserialize<'de>, 24 | { 25 | let s = Set::::deserialize(deserializer)?; 26 | Ok(s.elements) 27 | } 28 | 29 | #[derive(Debug, Clone, PartialEq)] 30 | struct Set { 31 | elements: T, 32 | } 33 | 34 | impl Serialize for Set 35 | where 36 | T: Serialize, 37 | { 38 | fn serialize(&self, serializer: S) -> Result 39 | where 40 | S: Serializer, 41 | { 42 | use serde::ser::SerializeStruct; 43 | 44 | let mut s = serializer.serialize_struct(TOKEN, 1)?; 45 | s.serialize_field(TOKEN, &self.elements)?; 46 | s.end() 47 | } 48 | } 49 | 50 | impl<'de, T> Deserialize<'de> for Set 51 | where 52 | T: Deserialize<'de>, 53 | { 54 | fn deserialize(deserializer: D) -> Result, D::Error> 55 | where 56 | D: Deserializer<'de>, 57 | { 58 | struct SetVisitor(PhantomData); 59 | 60 | impl<'de, T> de::Visitor<'de> for SetVisitor 61 | where 62 | T: Deserialize<'de>, 63 | { 64 | type Value = Set; 65 | 66 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 67 | formatter.write_str("a opa Set") 68 | } 69 | 70 | fn visit_map(self, mut visitor: V) -> Result, V::Error> 71 | where 72 | V: serde::de::MapAccess<'de>, 73 | { 74 | let key = visitor.next_key::()?; 75 | if key.is_none() { 76 | return Err(serde::de::Error::custom("set key not found")); 77 | } 78 | 79 | let elements: T = visitor.next_value()?; 80 | Ok(Set { elements }) 81 | } 82 | } 83 | 84 | static FIELDS: [&str; 1] = [TOKEN]; 85 | deserializer.deserialize_struct(TOKEN, &FIELDS, SetVisitor(PhantomData::default())) 86 | } 87 | } 88 | 89 | struct SetKey; 90 | 91 | impl<'de> Deserialize<'de> for SetKey { 92 | fn deserialize(deserializer: D) -> Result 93 | where 94 | D: Deserializer<'de>, 95 | { 96 | struct FieldVisitor; 97 | 98 | impl<'de> de::Visitor<'de> for FieldVisitor { 99 | type Value = (); 100 | 101 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 102 | formatter.write_str("a valid set field") 103 | } 104 | 105 | fn visit_str(self, s: &str) -> Result<(), E> 106 | where 107 | E: de::Error, 108 | { 109 | if s == TOKEN { 110 | Ok(()) 111 | } else { 112 | Err(de::Error::custom("expected field with custom name")) 113 | } 114 | } 115 | } 116 | 117 | deserializer.deserialize_identifier(FieldVisitor)?; 118 | Ok(SetKey) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /opa-wasm/src/value/de.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::de::{Deserialize, MapAccess, SeqAccess, Visitor}; 4 | 5 | use crate::set; 6 | use crate::value::{number, Map, Number, Value}; 7 | 8 | impl<'de> Deserialize<'de> for Value { 9 | #[inline] 10 | fn deserialize(deserializer: D) -> Result 11 | where 12 | D: serde::Deserializer<'de>, 13 | { 14 | struct ValueVisitor; 15 | 16 | impl<'de> Visitor<'de> for ValueVisitor { 17 | type Value = Value; 18 | 19 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 20 | formatter.write_str("any valid Rego value") 21 | } 22 | 23 | #[inline] 24 | fn visit_bool(self, value: bool) -> Result { 25 | Ok(Value::Bool(value)) 26 | } 27 | 28 | #[inline] 29 | fn visit_i64(self, value: i64) -> Result { 30 | Ok(Value::Number(value.into())) 31 | } 32 | 33 | #[inline] 34 | fn visit_u64(self, value: u64) -> Result { 35 | Ok(Value::Number(value.into())) 36 | } 37 | 38 | #[inline] 39 | fn visit_f64(self, value: f64) -> Result { 40 | Ok(Value::Number(value.into())) 41 | } 42 | 43 | #[inline] 44 | fn visit_str(self, value: &str) -> Result 45 | where 46 | E: serde::de::Error, 47 | { 48 | self.visit_string(String::from(value)) 49 | } 50 | 51 | #[inline] 52 | fn visit_string(self, value: String) -> Result { 53 | Ok(Value::String(value)) 54 | } 55 | 56 | #[inline] 57 | fn visit_none(self) -> Result { 58 | Ok(Value::Null) 59 | } 60 | 61 | #[inline] 62 | fn visit_some(self, deserializer: D) -> Result 63 | where 64 | D: serde::Deserializer<'de>, 65 | { 66 | Deserialize::deserialize(deserializer) 67 | } 68 | 69 | #[inline] 70 | fn visit_unit(self) -> Result { 71 | Ok(Value::Null) 72 | } 73 | 74 | #[inline] 75 | fn visit_seq(self, mut visitor: V) -> Result 76 | where 77 | V: SeqAccess<'de>, 78 | { 79 | let mut vec = Vec::new(); 80 | while let Some(elem) = visitor.next_element()? { 81 | vec.push(elem); 82 | } 83 | Ok(Value::Array(vec)) 84 | } 85 | 86 | fn visit_map(self, mut visitor: V) -> Result 87 | where 88 | V: MapAccess<'de>, 89 | { 90 | let value = match visitor.next_entry()? { 91 | Some((ref key, Value::Array(vec))) if key == set::TOKEN => { 92 | Value::Set(vec.into_iter().collect()) 93 | } 94 | Some((ref key, Value::String(s))) if key == number::TOKEN => { 95 | Value::Number(Number::from(s)) 96 | } 97 | Some((key, value)) => { 98 | let mut values = Map::new(); 99 | values.insert(key, value); 100 | while let Some((key, value)) = visitor.next_entry()? { 101 | values.insert(key, value); 102 | } 103 | Value::Object(values) 104 | } 105 | None => Value::Object(Map::new()), 106 | }; 107 | Ok(value) 108 | } 109 | } 110 | 111 | deserializer.deserialize_any(ValueVisitor) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /opa-wasm/src/value/from.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::iter::FromIterator; 3 | 4 | use super::{Map, Number, Set, Value}; 5 | 6 | macro_rules! from_integer { 7 | ($($ty:ident)*) => { 8 | $( 9 | impl From<$ty> for Value { 10 | fn from(n: $ty) -> Self { 11 | Value::Number(n.into()) 12 | } 13 | } 14 | )* 15 | }; 16 | } 17 | 18 | from_integer! { 19 | i8 i16 i32 i64 isize 20 | u8 u16 u32 u64 usize 21 | } 22 | 23 | impl From for Value { 24 | fn from(f: f32) -> Self { 25 | From::from(f as f64) 26 | } 27 | } 28 | 29 | impl From for Value { 30 | fn from(f: f64) -> Self { 31 | Number::from_f64(f).map_or(Value::Null, Value::Number) 32 | } 33 | } 34 | 35 | impl From for Value { 36 | fn from(b: bool) -> Self { 37 | Value::Bool(b) 38 | } 39 | } 40 | 41 | impl From for Value { 42 | fn from(s: String) -> Self { 43 | Value::String(s) 44 | } 45 | } 46 | 47 | impl<'a> From<&'a str> for Value { 48 | fn from(s: &str) -> Self { 49 | Value::String(s.to_string()) 50 | } 51 | } 52 | 53 | impl<'a> From> for Value { 54 | fn from(f: Cow<'a, str>) -> Self { 55 | Value::String(f.into_owned()) 56 | } 57 | } 58 | 59 | impl From for Value { 60 | fn from(f: Number) -> Self { 61 | Value::Number(f) 62 | } 63 | } 64 | 65 | impl From> for Value { 66 | fn from(f: Map) -> Self { 67 | Value::Object(f) 68 | } 69 | } 70 | 71 | impl From> for Value { 72 | fn from(f: Set) -> Self { 73 | Value::Set(f) 74 | } 75 | } 76 | 77 | impl> From> for Value { 78 | fn from(f: Vec) -> Self { 79 | Value::Array(f.into_iter().map(Into::into).collect()) 80 | } 81 | } 82 | 83 | impl<'a, T: Clone + Into> From<&'a [T]> for Value { 84 | fn from(f: &'a [T]) -> Self { 85 | Value::Array(f.iter().cloned().map(Into::into).collect()) 86 | } 87 | } 88 | 89 | impl> FromIterator for Value { 90 | fn from_iter>(iter: I) -> Self { 91 | Value::Array(iter.into_iter().map(Into::into).collect()) 92 | } 93 | } 94 | 95 | impl From<()> for Value { 96 | fn from((): ()) -> Self { 97 | Value::Null 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /opa-wasm/src/value/index.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::{fmt, ops}; 3 | 4 | use super::Value; 5 | 6 | pub trait Index: private::Sealed { 7 | #[doc(hidden)] 8 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value>; 9 | 10 | #[doc(hidden)] 11 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value>; 12 | 13 | #[doc(hidden)] 14 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value; 15 | } 16 | 17 | impl Index for usize { 18 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 19 | match v { 20 | Value::Array(ref vec) => vec.get(*self), 21 | _ => None, 22 | } 23 | } 24 | 25 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 26 | match v { 27 | Value::Array(ref mut vec) => vec.get_mut(*self), 28 | _ => None, 29 | } 30 | } 31 | 32 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 33 | match v { 34 | Value::Array(ref mut vec) => { 35 | let len = vec.len(); 36 | vec.get_mut(*self).unwrap_or_else(|| { 37 | panic!("cannot access index {} of array of length {}", self, len) 38 | }) 39 | } 40 | _ => panic!("cannot access index {} of value {}", self, Type(v)), 41 | } 42 | } 43 | } 44 | 45 | impl Index for str { 46 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 47 | match *v { 48 | Value::Object(ref map) => map.get(self), 49 | _ => None, 50 | } 51 | } 52 | 53 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 54 | match v { 55 | Value::Object(ref mut map) => map.get_mut(self), 56 | _ => None, 57 | } 58 | } 59 | 60 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 61 | if let Value::Null = *v { 62 | *v = Value::Object(BTreeMap::new()); 63 | } 64 | match v { 65 | Value::Object(ref mut map) => map.entry(self.to_owned()).or_insert(Value::Null), 66 | _ => panic!("cannot access key {:?} in value {}", self, Type(v)), 67 | } 68 | } 69 | } 70 | 71 | impl Index for String { 72 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 73 | self[..].index_into(v) 74 | } 75 | 76 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 77 | self[..].index_into_mut(v) 78 | } 79 | 80 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 81 | self[..].index_or_insert(v) 82 | } 83 | } 84 | 85 | impl<'a, T: ?Sized> Index for &'a T 86 | where 87 | T: Index, 88 | { 89 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 90 | (**self).index_into(v) 91 | } 92 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 93 | (**self).index_into_mut(v) 94 | } 95 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 96 | (**self).index_or_insert(v) 97 | } 98 | } 99 | 100 | mod private { 101 | pub trait Sealed {} 102 | impl Sealed for usize {} 103 | impl Sealed for str {} 104 | impl Sealed for String {} 105 | impl<'a, T: ?Sized> Sealed for &'a T where T: Sealed {} 106 | } 107 | 108 | struct Type<'a>(&'a Value); 109 | 110 | impl<'a> fmt::Display for Type<'a> { 111 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 112 | match *self.0 { 113 | Value::Null => formatter.write_str("null"), 114 | Value::Bool(_) => formatter.write_str("bool"), 115 | Value::Number(_) => formatter.write_str("number"), 116 | Value::String(_) => formatter.write_str("string"), 117 | Value::Array(_) => formatter.write_str("array"), 118 | Value::Object(_) => formatter.write_str("object"), 119 | Value::Set(_) => formatter.write_str("set"), 120 | } 121 | } 122 | } 123 | 124 | impl ops::Index for Value 125 | where 126 | I: Index, 127 | { 128 | type Output = Value; 129 | 130 | fn index(&self, index: I) -> &Value { 131 | static NULL: Value = Value::Null; 132 | index.index_into(self).unwrap_or(&NULL) 133 | } 134 | } 135 | 136 | impl ops::IndexMut for Value 137 | where 138 | I: Index, 139 | { 140 | fn index_mut(&mut self, index: I) -> &mut Value { 141 | index.index_or_insert(self) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /opa-wasm/src/value/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, BTreeSet}; 2 | use std::fmt; 3 | 4 | mod de; 5 | mod from; 6 | mod index; 7 | pub(crate) mod number; 8 | mod ser; 9 | 10 | use crate::error::Error; 11 | 12 | pub use self::index::Index; 13 | pub use self::number::Number; 14 | 15 | pub type Map = BTreeMap; 16 | pub type Set = BTreeSet; 17 | 18 | #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] 19 | pub enum Value { 20 | Null, 21 | Bool(bool), 22 | Number(Number), 23 | String(String), 24 | Array(Vec), 25 | Object(Map), 26 | Set(Set), 27 | } 28 | 29 | impl fmt::Debug for Value { 30 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 31 | match *self { 32 | Value::Null => formatter.debug_tuple("Null").finish(), 33 | Value::Bool(v) => formatter.debug_tuple("Bool").field(&v).finish(), 34 | Value::Number(ref v) => fmt::Debug::fmt(v, formatter), 35 | Value::String(ref v) => formatter.debug_tuple("String").field(v).finish(), 36 | Value::Array(ref v) => formatter.debug_tuple("Array").field(v).finish(), 37 | Value::Object(ref v) => formatter.debug_tuple("Object").field(v).finish(), 38 | Value::Set(ref v) => formatter.debug_tuple("Set").field(v).finish(), 39 | } 40 | } 41 | } 42 | 43 | impl fmt::Display for Value { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | match *self { 46 | Value::Null => write!(f, "null"), 47 | Value::Bool(ref v) => fmt::Display::fmt(v, f), 48 | Value::Number(ref v) => fmt::Display::fmt(v, f), 49 | Value::String(ref v) => write!(f, "\"{}\"", v.escape_default()), 50 | Value::Array(ref v) => { 51 | write!(f, "[")?; 52 | let mut iter = v.iter(); 53 | if let Some(first) = iter.next() { 54 | fmt::Display::fmt(first, f)?; 55 | } 56 | while let Some(elem) = iter.next() { 57 | write!(f, ",")?; 58 | fmt::Display::fmt(elem, f)?; 59 | } 60 | write!(f, "]") 61 | } 62 | Value::Object(ref v) => { 63 | write!(f, "{{")?; 64 | let mut iter = v.iter(); 65 | if let Some((k, v)) = iter.next() { 66 | fmt::Display::fmt(k, f)?; 67 | write!(f, ":")?; 68 | fmt::Display::fmt(v, f)?; 69 | } 70 | while let Some((k, v)) = iter.next() { 71 | write!(f, ",")?; 72 | fmt::Display::fmt(k, f)?; 73 | write!(f, ":")?; 74 | fmt::Display::fmt(v, f)?; 75 | } 76 | write!(f, "}}") 77 | } 78 | Value::Set(ref v) => { 79 | write!(f, "{{")?; 80 | let mut iter = v.iter(); 81 | if let Some(first) = iter.next() { 82 | fmt::Display::fmt(first, f)?; 83 | } 84 | while let Some(elem) = iter.next() { 85 | write!(f, ",")?; 86 | fmt::Display::fmt(elem, f)?; 87 | } 88 | write!(f, "}}") 89 | } 90 | } 91 | } 92 | } 93 | 94 | impl Default for Value { 95 | fn default() -> Value { 96 | Value::Null 97 | } 98 | } 99 | 100 | impl Value { 101 | pub fn get(&self, index: I) -> Option<&Value> { 102 | index.index_into(self) 103 | } 104 | 105 | pub fn get_mut(&mut self, index: I) -> Option<&mut Value> { 106 | index.index_into_mut(self) 107 | } 108 | 109 | pub fn try_into_set(self) -> Result, Error> { 110 | match self { 111 | Value::Set(v) => Ok(v), 112 | v => Err(Error::InvalidType("set", v)), 113 | } 114 | } 115 | 116 | pub fn as_set(&self) -> Option<&Set> { 117 | match *self { 118 | Value::Set(ref set) => Some(set), 119 | _ => None, 120 | } 121 | } 122 | 123 | pub fn as_set_mut(&mut self) -> Option<&mut Set> { 124 | match *self { 125 | Value::Set(ref mut set) => Some(set), 126 | _ => None, 127 | } 128 | } 129 | 130 | pub fn is_set(&self) -> bool { 131 | self.as_set().is_some() 132 | } 133 | 134 | pub fn try_into_object(self) -> Result, Error> { 135 | match self { 136 | Value::Object(map) => Ok(map), 137 | v => Err(Error::InvalidType("object", v)), 138 | } 139 | } 140 | 141 | pub fn as_object(&self) -> Option<&Map> { 142 | match *self { 143 | Value::Object(ref map) => Some(map), 144 | _ => None, 145 | } 146 | } 147 | 148 | pub fn as_object_mut(&mut self) -> Option<&mut Map> { 149 | match *self { 150 | Value::Object(ref mut map) => Some(map), 151 | _ => None, 152 | } 153 | } 154 | 155 | pub fn is_object(&self) -> bool { 156 | self.as_object().is_some() 157 | } 158 | 159 | pub fn try_into_array(self) -> Result, Error> { 160 | match self { 161 | Value::Array(array) => Ok(array), 162 | v => Err(Error::InvalidType("array", v)), 163 | } 164 | } 165 | 166 | pub fn as_array(&self) -> Option<&Vec> { 167 | match *self { 168 | Value::Array(ref array) => Some(array), 169 | _ => None, 170 | } 171 | } 172 | 173 | pub fn as_array_mut(&mut self) -> Option<&mut Vec> { 174 | match *self { 175 | Value::Array(ref mut array) => Some(array), 176 | _ => None, 177 | } 178 | } 179 | 180 | pub fn is_array(&self) -> bool { 181 | self.as_array().is_some() 182 | } 183 | 184 | pub fn try_into_string(self) -> Result { 185 | match self { 186 | Value::String(string) => Ok(string), 187 | v => Err(Error::InvalidType("string", v)), 188 | } 189 | } 190 | 191 | pub fn as_str(&self) -> Option<&str> { 192 | match *self { 193 | Value::String(ref string) => Some(string), 194 | _ => None, 195 | } 196 | } 197 | 198 | pub fn is_string(&self) -> bool { 199 | self.as_str().is_some() 200 | } 201 | 202 | pub fn is_number(&self) -> bool { 203 | match *self { 204 | Value::Number(_) => true, 205 | _ => false, 206 | } 207 | } 208 | 209 | pub fn try_into_i64(self) -> Result { 210 | match self { 211 | Value::Number(n) => n.try_into_i64(), 212 | v => Err(Error::InvalidType("i64", v)), 213 | } 214 | } 215 | 216 | pub fn as_i64(&self) -> Option { 217 | match *self { 218 | Value::Number(ref n) => n.as_i64(), 219 | _ => None, 220 | } 221 | } 222 | 223 | pub fn is_i64(&self) -> bool { 224 | match *self { 225 | Value::Number(ref n) => n.is_i64(), 226 | _ => false, 227 | } 228 | } 229 | 230 | pub fn try_into_f64(self) -> Result { 231 | match self { 232 | Value::Number(n) => n.try_into_f64(), 233 | v => Err(Error::InvalidType("f64", v)), 234 | } 235 | } 236 | 237 | pub fn as_f64(&self) -> Option { 238 | match *self { 239 | Value::Number(ref n) => n.as_f64(), 240 | _ => None, 241 | } 242 | } 243 | 244 | pub fn is_f64(&self) -> bool { 245 | match *self { 246 | Value::Number(ref n) => n.is_f64(), 247 | _ => false, 248 | } 249 | } 250 | 251 | pub fn try_into_bool(self) -> Result { 252 | match self { 253 | Value::Bool(b) => Ok(b), 254 | v => Err(Error::InvalidType("bool", v)), 255 | } 256 | } 257 | 258 | pub fn as_bool(&self) -> Option { 259 | match *self { 260 | Value::Bool(b) => Some(b), 261 | _ => None, 262 | } 263 | } 264 | 265 | pub fn is_boolean(&self) -> bool { 266 | self.as_bool().is_some() 267 | } 268 | 269 | pub fn as_null(&self) -> Option<()> { 270 | match *self { 271 | Value::Null => Some(()), 272 | _ => None, 273 | } 274 | } 275 | 276 | pub fn is_null(&self) -> bool { 277 | self.as_null().is_some() 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /opa-wasm/src/value/number.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use ordered_float::OrderedFloat; 4 | use serde::de::{self, Visitor}; 5 | use serde::{forward_to_deserialize_any, Deserialize, Deserializer, Serialize, Serializer}; 6 | 7 | use crate::Error; 8 | 9 | pub(crate) const TOKEN: &str = "$policy::value::private::Number"; 10 | 11 | #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] 12 | pub struct Number { 13 | n: N, 14 | } 15 | 16 | #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] 17 | enum N { 18 | Int(i64), 19 | Float(OrderedFloat), 20 | Ref(String), 21 | } 22 | 23 | impl Number { 24 | #[inline] 25 | pub fn is_i64(&self) -> bool { 26 | match &self.n { 27 | N::Int(_) => true, 28 | N::Float(_) => false, 29 | N::Ref(_) => self.as_i64().is_some(), 30 | } 31 | } 32 | 33 | #[inline] 34 | pub fn is_f64(&self) -> bool { 35 | match &self.n { 36 | N::Float(_) => true, 37 | N::Int(_) => false, 38 | N::Ref(ref s) => { 39 | for c in s.chars() { 40 | if c == '.' || c == 'e' || c == 'E' { 41 | return s.parse::().ok().map_or(false, |f| f.is_finite()); 42 | } 43 | } 44 | false 45 | } 46 | } 47 | } 48 | 49 | #[inline] 50 | pub fn try_into_i64(self) -> Result { 51 | match self.n { 52 | N::Int(n) => Ok(n), 53 | N::Float(_) => Err(Error::InvalidType("i64", self.into())), 54 | N::Ref(ref s) => s 55 | .parse() 56 | .map_err(|_| Error::InvalidType("i64", self.into())), 57 | } 58 | } 59 | 60 | #[inline] 61 | pub fn as_i64(&self) -> Option { 62 | match self.n { 63 | N::Int(n) => Some(n), 64 | N::Float(_) => None, 65 | N::Ref(ref s) => s.parse().ok(), 66 | } 67 | } 68 | 69 | #[inline] 70 | pub fn try_into_f64(self) -> Result { 71 | match self.n { 72 | N::Int(n) => Ok(n as f64), 73 | N::Float(f) => Ok(f.into_inner()), 74 | N::Ref(ref s) => s 75 | .parse() 76 | .map_err(|_| Error::InvalidType("f64", self.into())), 77 | } 78 | } 79 | 80 | #[inline] 81 | pub fn as_f64(&self) -> Option { 82 | match self.n { 83 | N::Int(n) => Some(n as f64), 84 | N::Float(f) => Some(f.into_inner()), 85 | N::Ref(ref s) => s.parse().ok(), 86 | } 87 | } 88 | 89 | #[inline] 90 | pub fn from_f64(f: f64) -> Option { 91 | if f.is_finite() { 92 | let n = N::Float(OrderedFloat(f)); 93 | Some(Number { n }) 94 | } else { 95 | None 96 | } 97 | } 98 | } 99 | 100 | impl fmt::Display for Number { 101 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 102 | match self.n { 103 | N::Int(i) => fmt::Display::fmt(&i, formatter), 104 | N::Float(f) => fmt::Display::fmt(&f, formatter), 105 | N::Ref(ref s) => fmt::Display::fmt(&s, formatter), 106 | } 107 | } 108 | } 109 | 110 | impl fmt::Debug for Number { 111 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 112 | let mut debug = formatter.debug_tuple("Number"); 113 | match self.n { 114 | N::Int(i) => { 115 | debug.field(&i); 116 | } 117 | N::Float(i) => { 118 | debug.field(&i); 119 | } 120 | N::Ref(ref s) => { 121 | debug.field(&s); 122 | } 123 | } 124 | debug.finish() 125 | } 126 | } 127 | 128 | macro_rules! impl_from_int { 129 | ( $($ty:ty),* ) => { 130 | $( 131 | impl From<$ty> for Number { 132 | #[inline] 133 | fn from(i: $ty) -> Self { 134 | let n = N::Int(i as i64); 135 | Number { n } 136 | } 137 | } 138 | )* 139 | } 140 | } 141 | 142 | impl_from_int!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize); 143 | 144 | macro_rules! impl_from_float { 145 | ( $($ty:ty),* ) => { 146 | $( 147 | impl From<$ty> for Number { 148 | #[inline] 149 | fn from(f: $ty) -> Self { 150 | let n = N::Float(OrderedFloat(f.into())); 151 | Number { n } 152 | } 153 | } 154 | )* 155 | } 156 | } 157 | 158 | impl_from_float!(f32, f64); 159 | 160 | impl From for Number { 161 | fn from(s: String) -> Self { 162 | let n = N::Ref(s); 163 | Number { n } 164 | } 165 | } 166 | 167 | impl Serialize for Number { 168 | #[inline] 169 | fn serialize(&self, serializer: S) -> Result 170 | where 171 | S: Serializer, 172 | { 173 | match self.n { 174 | N::Int(i) => serializer.serialize_i64(i), 175 | N::Float(f) => f.serialize(serializer), 176 | N::Ref(ref n) => { 177 | use serde::ser::SerializeStruct; 178 | let mut s = serializer.serialize_struct(TOKEN, 1)?; 179 | s.serialize_field(TOKEN, n)?; 180 | s.end() 181 | } 182 | } 183 | } 184 | } 185 | 186 | impl<'de> Deserialize<'de> for Number { 187 | #[inline] 188 | fn deserialize(deserializer: D) -> Result 189 | where 190 | D: Deserializer<'de>, 191 | { 192 | struct NumberVisitor; 193 | 194 | impl<'de> Visitor<'de> for NumberVisitor { 195 | type Value = Number; 196 | 197 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 198 | formatter.write_str("a Rego number") 199 | } 200 | 201 | #[inline] 202 | fn visit_i64(self, value: i64) -> Result { 203 | Ok(value.into()) 204 | } 205 | 206 | #[inline] 207 | fn visit_f64(self, value: f64) -> Result 208 | where 209 | E: de::Error, 210 | { 211 | Number::from_f64(value).ok_or_else(|| de::Error::custom("not a Rego number")) 212 | } 213 | 214 | fn visit_map(self, mut visitor: V) -> Result 215 | where 216 | V: de::MapAccess<'de>, 217 | { 218 | let value = visitor.next_key::()?; 219 | if value.is_none() { 220 | return Err(serde::de::Error::custom("number key not found")); 221 | } 222 | let v: NumberFromString = visitor.next_value()?; 223 | Ok(v.value) 224 | } 225 | } 226 | 227 | deserializer.deserialize_any(NumberVisitor) 228 | } 229 | } 230 | 231 | struct NumberKey; 232 | 233 | impl<'de> de::Deserialize<'de> for NumberKey { 234 | fn deserialize(deserializer: D) -> Result 235 | where 236 | D: de::Deserializer<'de>, 237 | { 238 | struct FieldVisitor; 239 | 240 | impl<'de> de::Visitor<'de> for FieldVisitor { 241 | type Value = (); 242 | 243 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 244 | formatter.write_str("a valid number field") 245 | } 246 | 247 | fn visit_str(self, s: &str) -> Result<(), E> 248 | where 249 | E: de::Error, 250 | { 251 | if s == TOKEN { 252 | Ok(()) 253 | } else { 254 | Err(de::Error::custom("expected field with custom name")) 255 | } 256 | } 257 | } 258 | 259 | deserializer.deserialize_identifier(FieldVisitor)?; 260 | Ok(NumberKey) 261 | } 262 | } 263 | 264 | pub struct NumberFromString { 265 | pub value: Number, 266 | } 267 | 268 | impl<'de> de::Deserialize<'de> for NumberFromString { 269 | fn deserialize(deserializer: D) -> Result 270 | where 271 | D: de::Deserializer<'de>, 272 | { 273 | struct Visitor; 274 | 275 | impl<'de> de::Visitor<'de> for Visitor { 276 | type Value = NumberFromString; 277 | 278 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 279 | formatter.write_str("string containing a number") 280 | } 281 | 282 | fn visit_string(self, s: String) -> Result 283 | where 284 | E: de::Error, 285 | { 286 | let n = N::Ref(s); 287 | let num = Number { n }; 288 | Ok(NumberFromString { value: num }) 289 | } 290 | } 291 | 292 | deserializer.deserialize_str(Visitor) 293 | } 294 | } 295 | 296 | macro_rules! deserialize_any { 297 | (@expand [$($num_string:tt)*]) => { 298 | #[inline] 299 | fn deserialize_any(self, visitor: V) -> Result 300 | where 301 | V: Visitor<'de>, 302 | { 303 | match self.n { 304 | N::Int(i) => visitor.visit_i64(i), 305 | N::Float(f) => visitor.visit_f64(f.into_inner()), 306 | N::Ref(ref s) => visitor.visit_str(s), 307 | } 308 | } 309 | }; 310 | 311 | (owned) => { 312 | deserialize_any!(@expand [n]); 313 | }; 314 | 315 | (ref) => { 316 | deserialize_any!(@expand [n.clone()]); 317 | }; 318 | } 319 | 320 | macro_rules! deserialize_number { 321 | ($deserialize:ident => $visit:ident) => { 322 | fn $deserialize(self, visitor: V) -> Result 323 | where 324 | V: Visitor<'de>, 325 | { 326 | self.deserialize_any(visitor) 327 | } 328 | }; 329 | } 330 | 331 | impl<'de> Deserializer<'de> for Number { 332 | type Error = Error; 333 | 334 | deserialize_any!(owned); 335 | 336 | deserialize_number!(deserialize_i8 => visit_i8); 337 | deserialize_number!(deserialize_i16 => visit_i16); 338 | deserialize_number!(deserialize_i32 => visit_i32); 339 | deserialize_number!(deserialize_i64 => visit_i64); 340 | deserialize_number!(deserialize_u8 => visit_u8); 341 | deserialize_number!(deserialize_u16 => visit_u16); 342 | deserialize_number!(deserialize_u32 => visit_u32); 343 | deserialize_number!(deserialize_u64 => visit_u64); 344 | deserialize_number!(deserialize_f32 => visit_f32); 345 | deserialize_number!(deserialize_f64 => visit_f64); 346 | 347 | forward_to_deserialize_any! { 348 | bool char str string bytes byte_buf option unit unit_struct 349 | newtype_struct seq tuple tuple_struct map struct enum identifier 350 | ignored_any 351 | } 352 | } 353 | 354 | impl<'de, 'a> Deserializer<'de> for &'a Number { 355 | type Error = Error; 356 | 357 | deserialize_any!(ref); 358 | 359 | deserialize_number!(deserialize_i8 => visit_i8); 360 | deserialize_number!(deserialize_i16 => visit_i16); 361 | deserialize_number!(deserialize_i32 => visit_i32); 362 | deserialize_number!(deserialize_i64 => visit_i64); 363 | deserialize_number!(deserialize_u8 => visit_u8); 364 | deserialize_number!(deserialize_u16 => visit_u16); 365 | deserialize_number!(deserialize_u32 => visit_u32); 366 | deserialize_number!(deserialize_u64 => visit_u64); 367 | deserialize_number!(deserialize_f32 => visit_f32); 368 | deserialize_number!(deserialize_f64 => visit_f64); 369 | 370 | forward_to_deserialize_any! { 371 | bool char str string bytes byte_buf option unit unit_struct 372 | newtype_struct seq tuple tuple_struct map struct enum identifier 373 | ignored_any 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /opa-wasm/src/value/ser.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::Serialize; 2 | 3 | use crate::set; 4 | use crate::value::Value; 5 | 6 | impl Serialize for Value { 7 | #[inline] 8 | fn serialize(&self, serializer: S) -> Result 9 | where 10 | S: serde::Serializer, 11 | { 12 | match *self { 13 | Value::Null => serializer.serialize_unit(), 14 | Value::Bool(b) => serializer.serialize_bool(b), 15 | Value::Number(ref n) => n.serialize(serializer), 16 | Value::String(ref s) => serializer.serialize_str(s), 17 | Value::Array(ref v) => v.serialize(serializer), 18 | Value::Object(ref m) => { 19 | use serde::ser::SerializeMap; 20 | let mut map = serializer.serialize_map(Some(m.len()))?; 21 | for (k, v) in m { 22 | map.serialize_entry(k, v)?; 23 | } 24 | map.end() 25 | } 26 | Value::Set(ref s) => { 27 | use serde::ser::SerializeStruct; 28 | let mut set = serializer.serialize_struct(set::TOKEN, 1)?; 29 | set.serialize_field(set::TOKEN, s)?; 30 | set.end() 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /opa-wasm/tests/empty.rego: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | default allow = false 4 | -------------------------------------------------------------------------------- /opa-wasm/tests/empty.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myagley/opa-rs/f85db53bd3a5b83e79837ef8b55c7e05adfef245/opa-wasm/tests/empty.wasm -------------------------------------------------------------------------------- /opa-wasm/tests/eval_struct_test.rego: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | default eval_struct = false 4 | 5 | eval_struct { 6 | #trace(input) 7 | input.byte == -1 8 | input.short == -257 9 | input.int == -65600 10 | input.long == -3000000000 11 | 12 | input.ubyte == 1 13 | input.ushort == 257 14 | input.uint == 65600 15 | input.ulong == 3000000000 16 | 17 | #input.float = 1.0499999523162842 18 | input.double == 2.34 19 | 20 | input.string == "this is a string" 21 | 22 | input.unit == null 23 | input.unit_struct == null 24 | input.newtype_struct == 3 25 | input.struc.a == 1 26 | input.struc.b == 2 27 | 28 | input.unit_variant == "unit" 29 | input.newtype_variant.new_type == 64 30 | input.tuple_variant.tuple = [42, "hello"] 31 | input.struct_variant.struct.age == 72 32 | input.struct_variant.struct.msg == "goodbye" 33 | 34 | input["some"] = "there's something here" 35 | input.none = null 36 | 37 | input.map[1] == 2 38 | input.map[2] == 3 39 | 40 | input.list == [1, 2, 3] 41 | input.list[0] == 1 42 | input.list[1] == 2 43 | input.list[2] == 3 44 | 45 | input.set | {"b", "a"} == {"a", "b"} 46 | is_set(input.set) 47 | } 48 | -------------------------------------------------------------------------------- /opa-wasm/tests/eval_struct_test.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::io; 3 | use std::path::PathBuf; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | use tracing::Level; 7 | use tracing_subscriber::{fmt, EnvFilter}; 8 | 9 | use opa_wasm::Policy; 10 | 11 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 12 | struct UnitStruct; 13 | 14 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 15 | struct NewtypeStruct(u8); 16 | 17 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 18 | struct Struct { 19 | a: u8, 20 | b: u8, 21 | } 22 | 23 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 24 | #[serde(rename_all = "snake_case")] 25 | enum TestEnum { 26 | Unit, 27 | NewType(i64), 28 | Tuple(i64, String), 29 | Struct { age: i64, msg: String }, 30 | } 31 | 32 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 33 | struct TestStruct { 34 | byte: i8, 35 | short: i16, 36 | int: i32, 37 | long: i64, 38 | 39 | ubyte: u8, 40 | ushort: u16, 41 | uint: u32, 42 | ulong: u64, 43 | 44 | float: f32, 45 | double: f64, 46 | 47 | string: String, 48 | 49 | unit: (), 50 | unit_struct: UnitStruct, 51 | newtype_struct: NewtypeStruct, 52 | struc: Struct, 53 | 54 | unit_variant: TestEnum, 55 | newtype_variant: TestEnum, 56 | tuple_variant: TestEnum, 57 | struct_variant: TestEnum, 58 | 59 | some: Option, 60 | none: Option, 61 | 62 | map: HashMap, 63 | list: Vec, 64 | #[serde(with = "opa_wasm::set")] 65 | set: HashSet, 66 | } 67 | 68 | #[test] 69 | fn test_eval() { 70 | let subscriber = fmt::Subscriber::builder() 71 | .with_ansi(atty::is(atty::Stream::Stderr)) 72 | .with_max_level(Level::TRACE) 73 | .with_writer(io::stderr) 74 | .with_env_filter(EnvFilter::from_default_env()) 75 | .finish(); 76 | let _ = tracing::subscriber::set_global_default(subscriber); 77 | 78 | let mut rego = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 79 | rego.push("tests/eval_struct_test.rego"); 80 | 81 | let mut map = HashMap::new(); 82 | map.insert(1, 2); 83 | map.insert(2, 3); 84 | 85 | let mut set = HashSet::new(); 86 | set.insert("a".to_string()); 87 | set.insert("b".to_string()); 88 | 89 | let module = opa_go::wasm::compile("data.tests.eval_struct", ®o).unwrap(); 90 | let mut policy = Policy::from_wasm(&module).unwrap(); 91 | let input = TestStruct { 92 | byte: -1, 93 | short: -257, 94 | int: -65_600, 95 | long: -3_000_000_000, 96 | 97 | ubyte: 1, 98 | ushort: 257, 99 | uint: 65_600, 100 | ulong: 3_000_000_000, 101 | 102 | float: 1.0499999523162842, 103 | double: 2.34, 104 | 105 | string: "this is a string".to_string(), 106 | 107 | unit: (), 108 | unit_struct: UnitStruct, 109 | newtype_struct: NewtypeStruct(3), 110 | struc: Struct { a: 1, b: 2 }, 111 | 112 | unit_variant: TestEnum::Unit, 113 | newtype_variant: TestEnum::NewType(64), 114 | tuple_variant: TestEnum::Tuple(42, "hello".to_string()), 115 | struct_variant: TestEnum::Struct { 116 | age: 72, 117 | msg: "goodbye".to_string(), 118 | }, 119 | 120 | some: Some("there's something here".to_string()), 121 | none: None, 122 | 123 | map, 124 | list: vec![1, 2, 3], 125 | set, 126 | }; 127 | let result = policy.evaluate(&input).unwrap(); 128 | assert_eq!(1, result.as_set().unwrap().len()); 129 | } 130 | -------------------------------------------------------------------------------- /opa-wasm/tests/types.rego: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | default types = false 4 | 5 | types = true { 6 | is_array(["a string", "another string"]) 7 | is_boolean(true) 8 | is_null(null) 9 | is_number(1.23) 10 | is_object({"key1":"value1", "key2":"value2"}) 11 | is_set({1, 3}) 12 | is_string("a string") 13 | 14 | is_array(array.concat([1], [2])) 15 | is_boolean(all([true])) 16 | is_number(1 + 2) 17 | is_object(object.remove({"key1": "value1"}, ["key1"])) 18 | is_set({1, 2} & {1, 3}) 19 | is_string(upper("lower")) 20 | } 21 | -------------------------------------------------------------------------------- /opa-wasm/tests/types.rs: -------------------------------------------------------------------------------- 1 | use opa_wasm::{Policy, Value}; 2 | 3 | #[test] 4 | fn test_types() { 5 | let module = opa_go::wasm::compile("data.tests.types", "tests/types.rego").unwrap(); 6 | let mut policy = Policy::from_wasm(&module).unwrap(); 7 | let result = policy.evaluate(&Value::Null).unwrap(); 8 | assert_eq!(1, result.as_set().unwrap().len()); 9 | } 10 | --------------------------------------------------------------------------------