use std::collections::HashSet;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::net::IpAddr;
#[cfg(all(target_family = "wasm", feature = "http"))]
use std::net::ToSocketAddrs;
#[cfg(feature = "surrealism")]
use std::str::FromStr;
#[cfg(feature = "surrealism")]
use anyhow::bail;
use ipnet::IpNet;
#[cfg(feature = "surrealism")]
use surrealism_runtime::capabilities::SurrealismCapabilities;
#[cfg(all(not(target_family = "wasm"), feature = "http"))]
use tokio::net::lookup_host;
use url::Url;
use crate::dbs::session::NewPlannerStrategy;
use crate::iam::{Auth, Level};
use crate::rpc::Method;
pub trait Target<Item: ?Sized = Self> {
fn matches(&self, elem: &Item) -> bool;
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct FuncTarget(pub String, pub Option<String>);
impl fmt::Display for FuncTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.1 {
Some(name) => write!(f, "{}:{name}", self.0),
None => write!(f, "{}::*", self.0),
}
}
}
impl Target for FuncTarget {
fn matches(&self, elem: &FuncTarget) -> bool {
match self {
Self(family, Some(name)) => {
family == &elem.0 && (elem.1.as_ref().is_some_and(|n| n == name))
}
Self(family, None) => family == &elem.0,
}
}
}
impl Target<str> for FuncTarget {
fn matches(&self, elem: &str) -> bool {
if let Some(x) = self.1.as_ref() {
let Some((f, r)) = elem.split_once("::") else {
return false;
};
f == self.0 && r == x
} else {
let f = elem.split_once("::").map(|(f, _)| f).unwrap_or(elem);
f == self.0
}
}
}
#[derive(Debug, Clone)]
pub enum ParseFuncTargetError {
InvalidWildcardFamily,
InvalidName,
}
impl std::error::Error for ParseFuncTargetError {}
impl fmt::Display for ParseFuncTargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ParseFuncTargetError::InvalidName => {
write!(f, "invalid function target name")
}
ParseFuncTargetError::InvalidWildcardFamily => {
write!(
f,
"invalid function target wildcard family, only first part of function can be wildcarded"
)
}
}
}
}
impl std::str::FromStr for FuncTarget {
type Err = ParseFuncTargetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s.is_empty() {
return Err(ParseFuncTargetError::InvalidName);
}
if let Some(family) = s.strip_suffix("::*") {
if family.contains("::") {
return Err(ParseFuncTargetError::InvalidWildcardFamily);
}
if !family.bytes().all(|x| x.is_ascii_alphanumeric()) {
return Err(ParseFuncTargetError::InvalidName);
}
return Ok(FuncTarget(family.to_string(), None));
}
if !s.bytes().all(|x| x.is_ascii_alphanumeric() || x == b':') {
return Err(ParseFuncTargetError::InvalidName);
}
if let Some((first, rest)) = s.split_once("::") {
Ok(FuncTarget(first.to_string(), Some(rest.to_string())))
} else {
Ok(FuncTarget(s.to_string(), None))
}
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum ExperimentalTarget {
Files,
Surrealism,
Gql,
}
impl fmt::Display for ExperimentalTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Files => write!(f, "files"),
Self::Surrealism => write!(f, "surrealism"),
Self::Gql => write!(f, "gql"),
}
}
}
impl Target for ExperimentalTarget {
fn matches(&self, elem: &ExperimentalTarget) -> bool {
self == elem
}
}
impl Target<str> for ExperimentalTarget {
fn matches(&self, elem: &str) -> bool {
match self {
Self::Files => elem.eq_ignore_ascii_case("files"),
Self::Surrealism => elem.eq_ignore_ascii_case("surrealism"),
Self::Gql => elem.eq_ignore_ascii_case("gql"),
}
}
}
#[derive(Debug, Clone)]
pub enum ParseExperimentalTargetError {
InvalidName,
}
impl std::error::Error for ParseExperimentalTargetError {}
impl fmt::Display for ParseExperimentalTargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ParseExperimentalTargetError::InvalidName => {
write!(f, "invalid experimental target name")
}
}
}
}
impl std::str::FromStr for ExperimentalTarget {
type Err = ParseExperimentalTargetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"files" => Ok(ExperimentalTarget::Files),
"surrealism" => Ok(ExperimentalTarget::Surrealism),
"gql" => Ok(ExperimentalTarget::Gql),
_ => Err(ParseExperimentalTargetError::InvalidName),
}
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum NetTarget {
Host(url::Host<String>, Option<u16>),
IPNet(IpNet),
}
#[cfg(feature = "http")]
impl NetTarget {
#[cfg(not(target_family = "wasm"))]
pub(crate) async fn resolve(&self) -> Result<Vec<Self>, std::io::Error> {
match self {
NetTarget::Host(h, p) => {
let r = lookup_host((h.to_string(), p.unwrap_or(80)))
.await?
.map(|a| NetTarget::IPNet(a.ip().into()))
.collect();
Ok(r)
}
NetTarget::IPNet(_) => Ok(vec![]),
}
}
#[cfg(target_family = "wasm")]
pub(crate) fn resolve(&self) -> Result<Vec<Self>, std::io::Error> {
match self {
NetTarget::Host(h, p) => {
let r = (h.to_string(), p.unwrap_or(80))
.to_socket_addrs()?
.map(|a| NetTarget::IPNet(a.ip().into()))
.collect();
Ok(r)
}
NetTarget::IPNet(_) => Ok(vec![]),
}
}
}
impl fmt::Display for NetTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Host(host, Some(port)) => write!(f, "{host}:{port}"),
Self::Host(host, None) => write!(f, "{host}"),
Self::IPNet(ipnet) => write!(f, "{ipnet}"),
}
}
}
impl Target for NetTarget {
fn matches(&self, tgt: &Self) -> bool {
match self {
Self::Host(host, Some(port)) => match tgt {
Self::Host(tgt_host, Some(tgt_port)) => host == tgt_host && port == tgt_port,
_ => false,
},
Self::Host(host, None) => match tgt {
Self::Host(tgt_host, _) => host == tgt_host,
_ => false,
},
Self::IPNet(ipnet) => match tgt {
Self::IPNet(tgt_ipnet) => ipnet.contains(tgt_ipnet),
Self::Host(host, _) => match host {
url::Host::Ipv4(ip) => ipnet.contains(&IpAddr::from(ip.to_owned())),
url::Host::Ipv6(ip) => ipnet.contains(&IpAddr::from(ip.to_owned())),
_ => false,
},
},
}
}
}
#[derive(Debug)]
pub struct ParseNetTargetError;
impl std::error::Error for ParseNetTargetError {}
impl fmt::Display for ParseNetTargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "The provided network target is not a valid host name, IP address or CIDR block")
}
}
impl std::str::FromStr for NetTarget {
type Err = ParseNetTargetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(ipnet) = s.parse::<IpNet>() {
return Ok(NetTarget::IPNet(ipnet));
}
if let Ok(ipnet) = s.parse::<IpAddr>() {
return Ok(NetTarget::IPNet(IpNet::from(ipnet)));
}
if let Ok(url) = Url::parse(format!("http://{s}").as_str())
&& let Some(host) = url.host()
{
if let Some(Ok(port)) = s.split(':').next_back().map(|p| p.parse::<u16>()) {
return Ok(NetTarget::Host(host.to_owned(), Some(port)));
} else {
return Ok(NetTarget::Host(host.to_owned(), None));
}
}
Err(ParseNetTargetError)
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct MethodTarget {
pub method: Method,
}
impl fmt::Display for MethodTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.method.to_str())
}
}
impl Target for MethodTarget {
fn matches(&self, elem: &Self) -> bool {
self.method == elem.method
}
}
#[derive(Debug)]
pub struct ParseMethodTargetError;
impl std::error::Error for ParseMethodTargetError {}
impl fmt::Display for ParseMethodTargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "The provided method target is not a valid RPC method")
}
}
impl std::str::FromStr for MethodTarget {
type Err = ParseMethodTargetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match Method::parse_case_insensitive(s) {
Method::Unknown => Err(ParseMethodTargetError),
method => Ok(MethodTarget {
method,
}),
}
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum RouteTarget {
Health,
Export,
Import,
Rpc,
Version,
Sync,
Sql,
Signin,
Signup,
Key,
Ml,
GraphQL,
Api,
Mcp,
Gql,
}
impl fmt::Display for RouteTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RouteTarget::Health => write!(f, "health"),
RouteTarget::Export => write!(f, "export"),
RouteTarget::Import => write!(f, "import"),
RouteTarget::Rpc => write!(f, "rpc"),
RouteTarget::Version => write!(f, "version"),
RouteTarget::Sync => write!(f, "sync"),
RouteTarget::Sql => write!(f, "sql"),
RouteTarget::Signin => write!(f, "signin"),
RouteTarget::Signup => write!(f, "signup"),
RouteTarget::Key => write!(f, "key"),
RouteTarget::Ml => write!(f, "ml"),
RouteTarget::GraphQL => write!(f, "graphql"),
RouteTarget::Api => write!(f, "api"),
RouteTarget::Mcp => write!(f, "mcp"),
RouteTarget::Gql => write!(f, "gql"),
}
}
}
impl Target for RouteTarget {
fn matches(&self, elem: &Self) -> bool {
*self == *elem
}
}
#[derive(Debug)]
pub struct ParseRouteTargetError;
impl std::error::Error for ParseRouteTargetError {}
impl fmt::Display for ParseRouteTargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "The provided route target is not a valid HTTP route")
}
}
impl std::str::FromStr for RouteTarget {
type Err = ParseRouteTargetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"health" => Ok(RouteTarget::Health),
"export" => Ok(RouteTarget::Export),
"import" => Ok(RouteTarget::Import),
"rpc" => Ok(RouteTarget::Rpc),
"version" => Ok(RouteTarget::Version),
"sync" => Ok(RouteTarget::Sync),
"sql" => Ok(RouteTarget::Sql),
"signin" => Ok(RouteTarget::Signin),
"signup" => Ok(RouteTarget::Signup),
"key" => Ok(RouteTarget::Key),
"ml" => Ok(RouteTarget::Ml),
"graphql" => Ok(RouteTarget::GraphQL),
"api" => Ok(RouteTarget::Api),
"mcp" => Ok(RouteTarget::Mcp),
"gql" => Ok(RouteTarget::Gql),
_ => Err(ParseRouteTargetError),
}
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum ArbitraryQueryTarget {
Guest,
Record,
System,
}
impl fmt::Display for ArbitraryQueryTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Guest => write!(f, "guest"),
Self::Record => write!(f, "record"),
Self::System => write!(f, "system"),
}
}
}
impl<'a> From<&'a Level> for ArbitraryQueryTarget {
fn from(level: &'a Level) -> Self {
match level {
Level::No => ArbitraryQueryTarget::Guest,
Level::Root => ArbitraryQueryTarget::System,
Level::Namespace(_) => ArbitraryQueryTarget::System,
Level::Database(_, _) => ArbitraryQueryTarget::System,
Level::Record(_, _, _) => ArbitraryQueryTarget::Record,
}
}
}
impl<'a> From<&'a Auth> for ArbitraryQueryTarget {
fn from(auth: &'a Auth) -> Self {
auth.level().into()
}
}
impl Target for ArbitraryQueryTarget {
fn matches(&self, elem: &ArbitraryQueryTarget) -> bool {
self == elem
}
}
impl Target<str> for ArbitraryQueryTarget {
fn matches(&self, elem: &str) -> bool {
match self {
Self::Guest => elem.eq_ignore_ascii_case("guest"),
Self::Record => elem.eq_ignore_ascii_case("record"),
Self::System => elem.eq_ignore_ascii_case("system"),
}
}
}
#[derive(Debug, Clone)]
pub enum ParseArbitraryQueryTargetError {
InvalidName,
}
impl std::error::Error for ParseArbitraryQueryTargetError {}
impl fmt::Display for ParseArbitraryQueryTargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ParseArbitraryQueryTargetError::InvalidName => {
write!(f, "invalid query target name")
}
}
}
}
impl std::str::FromStr for ArbitraryQueryTarget {
type Err = ParseArbitraryQueryTargetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"guest" => Ok(ArbitraryQueryTarget::Guest),
"record" => Ok(ArbitraryQueryTarget::Record),
"system" => Ok(ArbitraryQueryTarget::System),
_ => Err(ParseArbitraryQueryTargetError::InvalidName),
}
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum EvalQueryTarget {
Guest,
Record,
System,
}
impl fmt::Display for EvalQueryTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Guest => write!(f, "guest"),
Self::Record => write!(f, "record"),
Self::System => write!(f, "system"),
}
}
}
impl<'a> From<&'a Level> for EvalQueryTarget {
fn from(level: &'a Level) -> Self {
match level {
Level::No => EvalQueryTarget::Guest,
Level::Root => EvalQueryTarget::System,
Level::Namespace(_) => EvalQueryTarget::System,
Level::Database(_, _) => EvalQueryTarget::System,
Level::Record(_, _, _) => EvalQueryTarget::Record,
}
}
}
impl<'a> From<&'a Auth> for EvalQueryTarget {
fn from(auth: &'a Auth) -> Self {
auth.level().into()
}
}
impl Target for EvalQueryTarget {
fn matches(&self, elem: &EvalQueryTarget) -> bool {
self == elem
}
}
impl Target<str> for EvalQueryTarget {
fn matches(&self, elem: &str) -> bool {
match self {
Self::Guest => elem.eq_ignore_ascii_case("guest"),
Self::Record => elem.eq_ignore_ascii_case("record"),
Self::System => elem.eq_ignore_ascii_case("system"),
}
}
}
#[derive(Debug, Clone)]
pub enum ParseEvalQueryTargetError {
InvalidName,
}
impl std::error::Error for ParseEvalQueryTargetError {}
impl fmt::Display for ParseEvalQueryTargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ParseEvalQueryTargetError::InvalidName => {
write!(f, "invalid eval query target name")
}
}
}
}
impl std::str::FromStr for EvalQueryTarget {
type Err = ParseEvalQueryTargetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"guest" => Ok(EvalQueryTarget::Guest),
"record" => Ok(EvalQueryTarget::Record),
"system" => Ok(EvalQueryTarget::System),
_ => Err(ParseEvalQueryTargetError::InvalidName),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Targets<T: Hash + Eq + PartialEq + Ord> {
None,
Some(HashSet<T>),
All,
}
impl<T: Target + Hash + Eq + PartialEq + Ord> From<T> for Targets<T> {
fn from(t: T) -> Self {
let mut set = HashSet::new();
set.insert(t);
Self::Some(set)
}
}
impl<T: Hash + Eq + PartialEq + Ord + fmt::Debug + fmt::Display> Targets<T> {
pub(crate) fn matches<S>(&self, elem: &S) -> bool
where
S: ?Sized,
T: Target<S>,
{
match self {
Self::None => false,
Self::All => true,
Self::Some(targets) => targets.iter().any(|t| t.matches(elem)),
}
}
}
impl<T: Target + Hash + Eq + PartialEq + Ord + fmt::Display> fmt::Display for Targets<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::All => write!(f, "all"),
Self::Some(targets) => write!(
f,
"{}",
targets.iter().map(|t| t.to_string()).collect::<Vec<String>>().join(", ")
),
}
}
}
impl<T: Hash + Eq + PartialEq + Ord> Hash for Targets<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::None => state.write_u8(0),
Self::All => state.write_u8(1),
Self::Some(targets) => {
let mut sorted = targets.iter().collect::<Vec<&T>>();
sorted.sort();
sorted.hash(state);
}
}
}
}
#[derive(Debug, Clone, Hash)]
pub struct Capabilities {
scripting: bool,
guest_access: bool,
live_query_notifications: bool,
allow_funcs: Targets<FuncTarget>,
deny_funcs: Targets<FuncTarget>,
pub(crate) allow_net: Targets<NetTarget>,
pub(crate) deny_net: Targets<NetTarget>,
allow_rpc: Targets<MethodTarget>,
deny_rpc: Targets<MethodTarget>,
allow_http: Targets<RouteTarget>,
deny_http: Targets<RouteTarget>,
allow_experimental: Targets<ExperimentalTarget>,
deny_experimental: Targets<ExperimentalTarget>,
allow_arbitrary_query: Targets<ArbitraryQueryTarget>,
deny_arbitrary_query: Targets<ArbitraryQueryTarget>,
allow_eval_query: Targets<EvalQueryTarget>,
deny_eval_query: Targets<EvalQueryTarget>,
planner_strategy: NewPlannerStrategy,
}
impl fmt::Display for Capabilities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"scripting={}, guest_access={}, live_query_notifications={}, allow_funcs={}, deny_funcs={}, allow_net={}, deny_net={}, allow_rpc={}, deny_rpc={}, allow_http={}, deny_http={}, allow_experimental={}, deny_experimental={}, allow_arbitrary_query={}, deny_arbitrary_query={}, allow_eval_query={}, deny_eval_query={}, planner_strategy={}",
self.scripting,
self.guest_access,
self.live_query_notifications,
self.allow_funcs,
self.deny_funcs,
self.allow_net,
self.deny_net,
self.allow_rpc,
self.deny_rpc,
self.allow_http,
self.deny_http,
self.allow_experimental,
self.deny_experimental,
self.allow_arbitrary_query,
self.deny_arbitrary_query,
self.allow_eval_query,
self.deny_eval_query,
self.planner_strategy,
)
}
}
impl Default for Capabilities {
fn default() -> Self {
Self {
scripting: false,
guest_access: false,
live_query_notifications: true,
allow_funcs: Targets::All,
deny_funcs: Targets::None,
allow_net: Targets::None,
deny_net: Targets::None,
allow_rpc: Targets::All,
deny_rpc: Targets::None,
allow_http: Targets::All,
deny_http: Targets::None,
allow_experimental: Targets::None,
deny_experimental: Targets::None,
allow_arbitrary_query: Targets::All,
deny_arbitrary_query: Targets::None,
allow_eval_query: Targets::None,
deny_eval_query: Targets::None,
planner_strategy: NewPlannerStrategy::default(),
}
}
}
impl Capabilities {
pub fn all() -> Self {
Self {
scripting: true,
guest_access: true,
live_query_notifications: true,
allow_funcs: Targets::All,
deny_funcs: Targets::None,
allow_net: Targets::All,
deny_net: Targets::None,
allow_rpc: Targets::All,
deny_rpc: Targets::None,
allow_http: Targets::All,
deny_http: Targets::None,
allow_experimental: Targets::None,
deny_experimental: Targets::None,
allow_arbitrary_query: Targets::All,
deny_arbitrary_query: Targets::None,
allow_eval_query: Targets::None,
deny_eval_query: Targets::None,
planner_strategy: NewPlannerStrategy::default(),
}
}
pub fn none() -> Self {
Self {
scripting: false,
guest_access: false,
live_query_notifications: false,
allow_funcs: Targets::None,
deny_funcs: Targets::None,
allow_net: Targets::None,
deny_net: Targets::None,
allow_rpc: Targets::None,
deny_rpc: Targets::None,
allow_http: Targets::None,
deny_http: Targets::None,
allow_experimental: Targets::None,
deny_experimental: Targets::None,
allow_arbitrary_query: Targets::None,
deny_arbitrary_query: Targets::None,
allow_eval_query: Targets::None,
deny_eval_query: Targets::None,
planner_strategy: NewPlannerStrategy::default(),
}
}
pub fn with_scripting(mut self, scripting: bool) -> Self {
self.scripting = scripting;
self
}
pub fn with_guest_access(mut self, guest_access: bool) -> Self {
self.guest_access = guest_access;
self
}
pub fn with_live_query_notifications(mut self, live_query_notifications: bool) -> Self {
self.live_query_notifications = live_query_notifications;
self
}
pub fn with_functions(mut self, allow_funcs: Targets<FuncTarget>) -> Self {
self.allow_funcs = allow_funcs;
self
}
pub fn allowed_functions_mut(&mut self) -> &mut Targets<FuncTarget> {
&mut self.allow_funcs
}
pub fn without_functions(mut self, deny_funcs: Targets<FuncTarget>) -> Self {
self.deny_funcs = deny_funcs;
self
}
pub fn denied_functions_mut(&mut self) -> &mut Targets<FuncTarget> {
&mut self.deny_funcs
}
pub fn with_experimental(mut self, allow_experimental: Targets<ExperimentalTarget>) -> Self {
self.allow_experimental = allow_experimental;
self
}
pub fn allowed_experimental_features_mut(&mut self) -> &mut Targets<ExperimentalTarget> {
&mut self.allow_experimental
}
pub fn without_experimental(mut self, deny_experimental: Targets<ExperimentalTarget>) -> Self {
self.deny_experimental = deny_experimental;
self
}
pub fn denied_experimental_features_mut(&mut self) -> &mut Targets<ExperimentalTarget> {
&mut self.deny_experimental
}
pub fn with_arbitrary_query(
mut self,
allow_arbitrary_query: Targets<ArbitraryQueryTarget>,
) -> Self {
self.allow_arbitrary_query = allow_arbitrary_query;
self
}
pub fn without_arbitrary_query(
mut self,
deny_arbitrary_query: Targets<ArbitraryQueryTarget>,
) -> Self {
self.deny_arbitrary_query = deny_arbitrary_query;
self
}
pub fn with_eval_query(mut self, allow_eval_query: Targets<EvalQueryTarget>) -> Self {
self.allow_eval_query = allow_eval_query;
self
}
pub fn without_eval_query(mut self, deny_eval_query: Targets<EvalQueryTarget>) -> Self {
self.deny_eval_query = deny_eval_query;
self
}
pub fn with_network_targets(mut self, allow_net: Targets<NetTarget>) -> Self {
self.allow_net = allow_net;
self
}
pub fn allowed_network_targets_mut(&mut self) -> &mut Targets<NetTarget> {
&mut self.allow_net
}
pub fn without_network_targets(mut self, deny_net: Targets<NetTarget>) -> Self {
self.deny_net = deny_net;
self
}
pub fn denied_network_targets_ref(&self) -> &Targets<NetTarget> {
&self.deny_net
}
pub fn denied_network_targets_mut(&mut self) -> &mut Targets<NetTarget> {
&mut self.deny_net
}
pub fn with_rpc_methods(mut self, allow_rpc: Targets<MethodTarget>) -> Self {
self.allow_rpc = allow_rpc;
self
}
pub fn without_rpc_methods(mut self, deny_rpc: Targets<MethodTarget>) -> Self {
self.deny_rpc = deny_rpc;
self
}
pub fn with_http_routes(mut self, allow_http: Targets<RouteTarget>) -> Self {
self.allow_http = allow_http;
self
}
pub fn without_http_routes(mut self, deny_http: Targets<RouteTarget>) -> Self {
self.deny_http = deny_http;
self
}
pub fn with_planner_strategy(mut self, strategy: NewPlannerStrategy) -> Self {
self.planner_strategy = strategy;
self
}
pub fn planner_strategy(&self) -> &NewPlannerStrategy {
&self.planner_strategy
}
pub fn allows_scripting(&self) -> bool {
self.scripting
}
pub fn allows_guest_access(&self) -> bool {
self.guest_access
}
pub fn allows_live_query_notifications(&self) -> bool {
self.live_query_notifications
}
pub fn allows_function_name(&self, target: &str) -> bool {
self.allow_funcs.matches(target) && !self.deny_funcs.matches(target)
}
pub fn allows_function_target(&self, target: &FuncTarget) -> bool {
self.allow_funcs.matches(target) && !self.deny_funcs.matches(target)
}
pub fn allows_experimental(&self, target: &ExperimentalTarget) -> bool {
self.allow_experimental.matches(target) && !self.deny_experimental.matches(target)
}
pub fn allows_experimental_name(&self, target: &str) -> bool {
self.allow_experimental.matches(target) && !self.deny_experimental.matches(target)
}
pub fn allows_query(&self, target: &ArbitraryQueryTarget) -> bool {
self.allow_arbitrary_query.matches(target) && !self.deny_arbitrary_query.matches(target)
}
pub fn allows_eval_query(&self, target: &EvalQueryTarget) -> bool {
self.allow_eval_query.matches(target) && !self.deny_eval_query.matches(target)
}
pub fn allows_network_target(&self, target: &NetTarget) -> bool {
self.allow_net.matches(target) && !self.deny_net.matches(target)
}
#[cfg(feature = "http")]
pub(crate) fn matches_any_allow_net(&self, target: &NetTarget) -> bool {
self.allow_net.matches(target)
}
#[cfg(feature = "http")]
pub(crate) fn matches_any_deny_net(&self, target: &NetTarget) -> bool {
self.deny_net.matches(target)
}
pub fn allows_rpc_method(&self, target: &MethodTarget) -> bool {
self.allow_rpc.matches(target) && !self.deny_rpc.matches(target)
}
pub fn allows_http_route(&self, target: &RouteTarget) -> bool {
self.allow_http.matches(target) && !self.deny_http.matches(target)
}
#[cfg(feature = "surrealism")]
pub fn validate_surrealism_capabilities(
&self,
capabilities: &SurrealismCapabilities,
) -> anyhow::Result<()> {
use surrealism_runtime::capabilities::FunctionTargets;
if capabilities.allow_scripting && !self.allows_scripting() {
bail!("Surrealism package requires scripting, but it is not allowed");
}
match &capabilities.allow_functions {
FunctionTargets::None => {}
FunctionTargets::All => {
if !matches!(self.allow_funcs, Targets::All) {
bail!(
"Surrealism package requires access to all functions, but the server does not allow all functions"
);
}
}
FunctionTargets::Some(patterns) => {
for pattern in patterns {
let target = FuncTarget::from_str(pattern).map_err(|e| {
anyhow::anyhow!(
"Surrealism package has invalid function pattern '{}': {}",
pattern,
e
)
})?;
if !self.allows_function_target(&target) {
bail!(
"Surrealism package requires function pattern '{}', but it is not allowed by the server",
pattern
);
}
}
}
}
if !capabilities.allow_net.is_empty() {
for net in capabilities.allow_net.iter() {
let target = NetTarget::from_str(net)?;
if !self.allows_network_target(&target) {
bail!(
"Surrealism package requires network target '{}', but it is not allowed",
net
);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use test_log::test;
use super::*;
#[test]
fn test_invalid_func_target() {
FuncTarget::from_str("te::*st").unwrap_err();
FuncTarget::from_str("\0::st").unwrap_err();
FuncTarget::from_str("").unwrap_err();
FuncTarget::from_str("❤️").unwrap_err();
}
#[test]
fn test_func_target() {
assert!(FuncTarget::from_str("test").unwrap().matches("test"));
assert!(!FuncTarget::from_str("test").unwrap().matches("test2"));
assert!(!FuncTarget::from_str("test::").unwrap().matches("test"));
assert!(FuncTarget::from_str("test::*").unwrap().matches("test::name"));
assert!(!FuncTarget::from_str("test::*").unwrap().matches("test2::name"));
assert!(FuncTarget::from_str("test::name").unwrap().matches("test::name"));
assert!(!FuncTarget::from_str("test::name").unwrap().matches("test::name2"));
}
#[test]
fn test_experimental_target() {
assert_eq!(ExperimentalTarget::from_str("files").unwrap(), ExperimentalTarget::Files);
assert_eq!(
ExperimentalTarget::from_str("surrealism").unwrap(),
ExperimentalTarget::Surrealism
);
assert_eq!(ExperimentalTarget::from_str("gql").unwrap(), ExperimentalTarget::Gql);
assert_eq!(ExperimentalTarget::from_str("GQL").unwrap(), ExperimentalTarget::Gql);
ExperimentalTarget::from_str("opengql").unwrap_err();
ExperimentalTarget::from_str("").unwrap_err();
assert_eq!(ExperimentalTarget::Gql.to_string(), "gql");
assert_eq!(
ExperimentalTarget::from_str(&ExperimentalTarget::Gql.to_string()).unwrap(),
ExperimentalTarget::Gql
);
assert!(ExperimentalTarget::Gql.matches("gql"));
assert!(ExperimentalTarget::Gql.matches("GQL"));
assert!(!ExperimentalTarget::Gql.matches("files"));
assert!(!ExperimentalTarget::Files.matches("gql"));
}
#[test]
fn test_net_target() {
assert!(
NetTarget::from_str("10.0.0.0/8")
.unwrap()
.matches(&NetTarget::from_str("10.0.1.0/24").unwrap())
);
assert!(
NetTarget::from_str("10.0.0.0/8")
.unwrap()
.matches(&NetTarget::from_str("10.0.1.2").unwrap())
);
assert!(
!NetTarget::from_str("10.0.0.0/8")
.unwrap()
.matches(&NetTarget::from_str("20.0.1.0/24").unwrap())
);
assert!(
!NetTarget::from_str("10.0.0.0/8")
.unwrap()
.matches(&NetTarget::from_str("20.0.1.0").unwrap())
);
assert!(
NetTarget::from_str("2001:db8::1")
.unwrap()
.matches(&NetTarget::from_str("2001:db8::1").unwrap())
);
assert!(
NetTarget::from_str("2001:db8::/32")
.unwrap()
.matches(&NetTarget::from_str("2001:db8::1").unwrap())
);
assert!(
NetTarget::from_str("2001:db8::/32")
.unwrap()
.matches(&NetTarget::from_str("2001:db8:abcd:12::/64").unwrap())
);
assert!(
!NetTarget::from_str("2001:db8::/32")
.unwrap()
.matches(&NetTarget::from_str("2001:db9::1").unwrap())
);
assert!(
!NetTarget::from_str("2001:db8::/32")
.unwrap()
.matches(&NetTarget::from_str("2001:db9:abcd:12::1/64").unwrap())
);
assert!(
NetTarget::from_str("example.com")
.unwrap()
.matches(&NetTarget::from_str("example.com").unwrap())
);
assert!(
NetTarget::from_str("example.com")
.unwrap()
.matches(&NetTarget::from_str("example.com:80").unwrap())
);
assert!(
!NetTarget::from_str("example.com")
.unwrap()
.matches(&NetTarget::from_str("www.example.com").unwrap())
);
assert!(
!NetTarget::from_str("example.com")
.unwrap()
.matches(&NetTarget::from_str("www.example.com:80").unwrap())
);
assert!(
NetTarget::from_str("example.com:80")
.unwrap()
.matches(&NetTarget::from_str("example.com:80").unwrap())
);
assert!(
!NetTarget::from_str("example.com:80")
.unwrap()
.matches(&NetTarget::from_str("example.com:443").unwrap())
);
assert!(
!NetTarget::from_str("example.com:80")
.unwrap()
.matches(&NetTarget::from_str("example.com").unwrap())
);
assert!(
NetTarget::from_str("127.0.0.1")
.unwrap()
.matches(&NetTarget::from_str("127.0.0.1").unwrap()),
"Host IPv4 without port matches itself"
);
assert!(
NetTarget::from_str("127.0.0.1")
.unwrap()
.matches(&NetTarget::from_str("127.0.0.1:80").unwrap()),
"Host IPv4 without port matches Host IPv4 with port"
);
assert!(
NetTarget::from_str("10.0.0.0/8")
.unwrap()
.matches(&NetTarget::from_str("10.0.0.1:80").unwrap()),
"IPv4 network matches Host IPv4 with port"
);
assert!(
NetTarget::from_str("127.0.0.1:80")
.unwrap()
.matches(&NetTarget::from_str("127.0.0.1:80").unwrap()),
"Host IPv4 with port matches itself"
);
assert!(
!NetTarget::from_str("127.0.0.1:80")
.unwrap()
.matches(&NetTarget::from_str("127.0.0.1").unwrap()),
"Host IPv4 with port does not match Host IPv4 without port"
);
assert!(
!NetTarget::from_str("127.0.0.1:80")
.unwrap()
.matches(&NetTarget::from_str("127.0.0.1:443").unwrap()),
"Host IPv4 with port does not match Host IPv4 with different port"
);
assert!(
NetTarget::from_str("[2001:db8::1]")
.unwrap()
.matches(&NetTarget::from_str("[2001:db8::1]").unwrap()),
"Host IPv6 without port matches itself"
);
assert!(
NetTarget::from_str("[2001:db8::1]")
.unwrap()
.matches(&NetTarget::from_str("[2001:db8::1]:80").unwrap()),
"Host IPv6 without port matches Host IPv6 with port"
);
assert!(
NetTarget::from_str("2001:db8::1")
.unwrap()
.matches(&NetTarget::from_str("[2001:db8::1]:80").unwrap()),
"IPv6 addr matches Host IPv6 with port"
);
assert!(
NetTarget::from_str("2001:db8::/64")
.unwrap()
.matches(&NetTarget::from_str("[2001:db8::1]:80").unwrap()),
"IPv6 network matches Host IPv6 with port"
);
assert!(
NetTarget::from_str("[2001:db8::1]:80")
.unwrap()
.matches(&NetTarget::from_str("[2001:db8::1]:80").unwrap()),
"Host IPv6 with port matches itself"
);
assert!(
!NetTarget::from_str("[2001:db8::1]:80")
.unwrap()
.matches(&NetTarget::from_str("[2001:db8::1]").unwrap()),
"Host IPv6 with port does not match Host IPv6 without port"
);
assert!(
!NetTarget::from_str("[2001:db8::1]:80")
.unwrap()
.matches(&NetTarget::from_str("[2001:db8::1]:443").unwrap()),
"Host IPv6 with port does not match Host IPv6 with different port"
);
assert!(NetTarget::from_str("exam^ple.com").is_err());
assert!(NetTarget::from_str("example.com:80:80").is_err());
assert!(NetTarget::from_str("11111.3.4.5").is_err());
assert!(NetTarget::from_str("2001:db8::1/129").is_err());
assert!(NetTarget::from_str("[2001:db8::1").is_err());
}
#[tokio::test]
#[cfg(all(not(target_family = "wasm"), feature = "http"))]
async fn test_net_target_resolve_async() {
let r = NetTarget::from_str("localhost").unwrap().resolve().await.unwrap();
let has_ipv4 = r.contains(&NetTarget::from_str("127.0.0.1").unwrap());
let has_ipv6 = r.contains(&NetTarget::from_str("::1/128").unwrap());
assert!(
has_ipv4 || has_ipv6,
"Expected localhost to resolve to at least 127.0.0.1 or ::1, got: {:?}",
r
);
}
#[test]
#[cfg(all(target_family = "wasm", feature = "http"))]
fn test_net_target_resolve_sync() {
let r = NetTarget::from_str("localhost").unwrap().resolve().unwrap();
let has_ipv4 = r.contains(&NetTarget::from_str("127.0.0.1").unwrap());
let has_ipv6 = r.contains(&NetTarget::from_str("::1/128").unwrap());
assert!(
has_ipv4 || has_ipv6,
"Expected localhost to resolve to at least 127.0.0.1 or ::1, got: {:?}",
r
);
}
#[test]
fn test_method_target() {
assert!(
MethodTarget::from_str("query")
.unwrap()
.matches(&MethodTarget::from_str("query").unwrap())
);
assert!(
MethodTarget::from_str("query")
.unwrap()
.matches(&MethodTarget::from_str("Query").unwrap())
);
assert!(
MethodTarget::from_str("query")
.unwrap()
.matches(&MethodTarget::from_str("QUERY").unwrap())
);
assert!(
!MethodTarget::from_str("query")
.unwrap()
.matches(&MethodTarget::from_str("ping").unwrap())
);
}
#[test]
fn test_targets() {
assert!(Targets::<NetTarget>::All.matches(&NetTarget::from_str("example.com").unwrap()));
assert!(Targets::<FuncTarget>::All.matches("http::get"));
assert!(!Targets::<NetTarget>::None.matches(&NetTarget::from_str("example.com").unwrap()));
assert!(!Targets::<FuncTarget>::None.matches("http::get"));
assert!(
Targets::<NetTarget>::Some([NetTarget::from_str("example.com").unwrap()].into())
.matches(&NetTarget::from_str("example.com").unwrap())
);
assert!(
!Targets::<NetTarget>::Some([NetTarget::from_str("example.com").unwrap()].into())
.matches(&NetTarget::from_str("www.example.com").unwrap())
);
assert!(
Targets::<FuncTarget>::Some([FuncTarget::from_str("http::get").unwrap()].into())
.matches("http::get")
);
assert!(
!Targets::<FuncTarget>::Some([FuncTarget::from_str("http::get").unwrap()].into())
.matches("http::post")
);
}
#[test]
fn test_capabilities() {
{
let caps = Capabilities::default().with_scripting(true);
assert!(caps.allows_scripting());
}
{
let caps = Capabilities::default().with_scripting(false);
assert!(!caps.allows_scripting());
}
{
let caps = Capabilities::default().with_guest_access(true);
assert!(caps.allows_guest_access());
}
{
let caps = Capabilities::default().with_guest_access(false);
assert!(!caps.allows_guest_access());
}
{
let cap = Capabilities::default().with_live_query_notifications(true);
assert!(cap.allows_live_query_notifications());
}
{
let cap = Capabilities::default().with_live_query_notifications(false);
assert!(!cap.allows_live_query_notifications());
}
{
let caps = Capabilities::default()
.with_network_targets(Targets::<NetTarget>::All)
.without_network_targets(Targets::<NetTarget>::None);
assert!(caps.allows_network_target(&NetTarget::from_str("example.com").unwrap()));
assert!(caps.allows_network_target(&NetTarget::from_str("example.com:80").unwrap()));
}
{
let caps = Capabilities::default()
.with_network_targets(Targets::<NetTarget>::All)
.without_network_targets(Targets::<NetTarget>::All);
assert!(!caps.allows_network_target(&NetTarget::from_str("example.com").unwrap()));
assert!(!caps.allows_network_target(&NetTarget::from_str("example.com:80").unwrap()));
}
{
let caps = Capabilities::default()
.with_network_targets(Targets::<NetTarget>::Some(
[NetTarget::from_str("example.com").unwrap()].into(),
))
.without_network_targets(Targets::<NetTarget>::Some(
[NetTarget::from_str("example.com:80").unwrap()].into(),
));
assert!(caps.allows_network_target(&NetTarget::from_str("example.com").unwrap()));
assert!(caps.allows_network_target(&NetTarget::from_str("example.com:443").unwrap()));
assert!(!caps.allows_network_target(&NetTarget::from_str("example.com:80").unwrap()));
}
{
let caps = Capabilities::default()
.with_functions(Targets::<FuncTarget>::All)
.without_functions(Targets::<FuncTarget>::None);
assert!(caps.allows_function_name("http::get"));
assert!(caps.allows_function_name("http::post"));
}
{
let caps = Capabilities::default()
.with_functions(Targets::<FuncTarget>::All)
.without_functions(Targets::<FuncTarget>::All);
assert!(!caps.allows_function_name("http::get"));
assert!(!caps.allows_function_name("http::post"));
}
{
let caps = Capabilities::default()
.with_functions(Targets::<FuncTarget>::Some(
[FuncTarget::from_str("http::*").unwrap()].into(),
))
.without_functions(Targets::<FuncTarget>::Some(
[FuncTarget::from_str("http::post").unwrap()].into(),
));
assert!(caps.allows_function_name("http::get"));
assert!(caps.allows_function_name("http::put"));
assert!(!caps.allows_function_name("http::post"));
}
{
let caps = Capabilities::default()
.with_rpc_methods(Targets::<MethodTarget>::All)
.without_rpc_methods(Targets::<MethodTarget>::None);
assert!(caps.allows_rpc_method(&MethodTarget::from_str("ping").unwrap()));
assert!(caps.allows_rpc_method(&MethodTarget::from_str("select").unwrap()));
assert!(caps.allows_rpc_method(&MethodTarget::from_str("query").unwrap()));
}
{
let caps = Capabilities::default()
.with_rpc_methods(Targets::<MethodTarget>::All)
.without_rpc_methods(Targets::<MethodTarget>::All);
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("ping").unwrap()));
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("select").unwrap()));
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("query").unwrap()));
}
{
let caps = Capabilities::default().without_rpc_methods(Targets::<MethodTarget>::All);
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("ping").unwrap()));
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("select").unwrap()));
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("query").unwrap()));
}
{
let caps = Capabilities::default()
.with_rpc_methods(Targets::<MethodTarget>::Some(
[
MethodTarget::from_str("select").unwrap(),
MethodTarget::from_str("create").unwrap(),
MethodTarget::from_str("insert").unwrap(),
MethodTarget::from_str("update").unwrap(),
MethodTarget::from_str("query").unwrap(),
MethodTarget::from_str("run").unwrap(),
]
.into(),
))
.without_rpc_methods(Targets::<MethodTarget>::Some(
[
MethodTarget::from_str("query").unwrap(),
MethodTarget::from_str("run").unwrap(),
]
.into(),
));
assert!(caps.allows_rpc_method(&MethodTarget::from_str("select").unwrap()));
assert!(caps.allows_rpc_method(&MethodTarget::from_str("create").unwrap()));
assert!(caps.allows_rpc_method(&MethodTarget::from_str("insert").unwrap()));
assert!(caps.allows_rpc_method(&MethodTarget::from_str("update").unwrap()));
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("query").unwrap()));
assert!(!caps.allows_rpc_method(&MethodTarget::from_str("run").unwrap()));
}
{
let caps = Capabilities::default()
.with_http_routes(Targets::<RouteTarget>::All)
.without_http_routes(Targets::<RouteTarget>::None);
assert!(caps.allows_http_route(&RouteTarget::from_str("version").unwrap()));
assert!(caps.allows_http_route(&RouteTarget::from_str("export").unwrap()));
assert!(caps.allows_http_route(&RouteTarget::from_str("sql").unwrap()));
}
{
let caps = Capabilities::default()
.with_http_routes(Targets::<RouteTarget>::All)
.without_http_routes(Targets::<RouteTarget>::All);
assert!(!caps.allows_http_route(&RouteTarget::from_str("version").unwrap()));
assert!(!caps.allows_http_route(&RouteTarget::from_str("export").unwrap()));
assert!(!caps.allows_http_route(&RouteTarget::from_str("sql").unwrap()));
}
{
let caps = Capabilities::default().without_http_routes(Targets::<RouteTarget>::All);
assert!(!caps.allows_http_route(&RouteTarget::from_str("version").unwrap()));
assert!(!caps.allows_http_route(&RouteTarget::from_str("export").unwrap()));
assert!(!caps.allows_http_route(&RouteTarget::from_str("sql").unwrap()));
}
{
let caps = Capabilities::default()
.with_http_routes(Targets::<RouteTarget>::Some(
[
RouteTarget::from_str("version").unwrap(),
RouteTarget::from_str("import").unwrap(),
RouteTarget::from_str("export").unwrap(),
RouteTarget::from_str("key").unwrap(),
RouteTarget::from_str("sql").unwrap(),
RouteTarget::from_str("rpc").unwrap(),
]
.into(),
))
.without_http_routes(Targets::<RouteTarget>::Some(
[RouteTarget::from_str("sql").unwrap(), RouteTarget::from_str("rpc").unwrap()]
.into(),
));
assert!(caps.allows_http_route(&RouteTarget::from_str("version").unwrap()));
assert!(caps.allows_http_route(&RouteTarget::from_str("import").unwrap()));
assert!(caps.allows_http_route(&RouteTarget::from_str("export").unwrap()));
assert!(caps.allows_http_route(&RouteTarget::from_str("key").unwrap()));
assert!(!caps.allows_http_route(&RouteTarget::from_str("sql").unwrap()));
assert!(!caps.allows_http_route(&RouteTarget::from_str("rpc").unwrap()));
}
{
let caps = Capabilities::default()
.with_arbitrary_query(Targets::<ArbitraryQueryTarget>::All)
.without_arbitrary_query(Targets::<ArbitraryQueryTarget>::None);
assert!(caps.allows_query(&ArbitraryQueryTarget::from_str("guest").unwrap()));
assert!(caps.allows_query(&ArbitraryQueryTarget::from_str("record").unwrap()));
assert!(caps.allows_query(&ArbitraryQueryTarget::from_str("system").unwrap()));
}
{
let caps = Capabilities::default()
.with_arbitrary_query(Targets::<ArbitraryQueryTarget>::All)
.without_arbitrary_query(Targets::<ArbitraryQueryTarget>::All);
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("guest").unwrap()));
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("record").unwrap()));
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("system").unwrap()));
}
{
let caps = Capabilities::default()
.without_arbitrary_query(Targets::<ArbitraryQueryTarget>::All);
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("guest").unwrap()));
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("record").unwrap()));
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("system").unwrap()));
}
{
let caps = Capabilities::default()
.with_arbitrary_query(Targets::<ArbitraryQueryTarget>::Some(
[
ArbitraryQueryTarget::from_str("guest").unwrap(),
ArbitraryQueryTarget::from_str("record").unwrap(),
]
.into(),
))
.without_arbitrary_query(Targets::<ArbitraryQueryTarget>::Some(
[
ArbitraryQueryTarget::from_str("record").unwrap(),
ArbitraryQueryTarget::from_str("system").unwrap(),
]
.into(),
));
assert!(caps.allows_query(&ArbitraryQueryTarget::from_str("guest").unwrap()));
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("record").unwrap()));
assert!(!caps.allows_query(&ArbitraryQueryTarget::from_str("system").unwrap()));
}
}
#[test]
fn test_eval_query() {
for caps in [Capabilities::default(), Capabilities::all(), Capabilities::none()] {
assert!(!caps.allows_eval_query(&EvalQueryTarget::from_str("guest").unwrap()));
assert!(!caps.allows_eval_query(&EvalQueryTarget::from_str("record").unwrap()));
assert!(!caps.allows_eval_query(&EvalQueryTarget::from_str("system").unwrap()));
}
{
let caps = Capabilities::all()
.with_eval_query(Targets::<EvalQueryTarget>::from(EvalQueryTarget::System));
assert!(caps.allows_eval_query(&EvalQueryTarget::System));
assert!(!caps.allows_eval_query(&EvalQueryTarget::Record));
assert!(!caps.allows_eval_query(&EvalQueryTarget::Guest));
}
{
let caps = Capabilities::all()
.with_eval_query(Targets::<EvalQueryTarget>::All)
.without_eval_query(Targets::<EvalQueryTarget>::from(EvalQueryTarget::Record));
assert!(caps.allows_eval_query(&EvalQueryTarget::System));
assert!(!caps.allows_eval_query(&EvalQueryTarget::Record));
}
}
}