aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/package
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-07 07:26:48 +0900
committernsfisis <nsfisis@gmail.com>2026-08-07 07:26:48 +0900
commitf749a47804cd296a3059cd3f8079c62dbaa5fdc0 (patch)
treea84d5d40f6f9eea2a83355a273d0fb57214a864a /crates/shirabe/src/package
parente7f83b74e8f8c12b4a1b0f9f613387b03858dbdd (diff)
downloadphp-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.tar.gz
php-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.tar.zst
php-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.zip
refactor: merge split inherent impl blocks into one per type
Enable clippy::multiple_inherent_impl and fix the 21 sites it reports. Types whose inherent methods were spread across two or three impl blocks now keep them in a single block; only the impl headers move, no method bodies change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/package')
-rw-r--r--crates/shirabe/src/package/loader/array_loader.rs296
-rw-r--r--crates/shirabe/src/package/loader/validating_array_loader.rs554
2 files changed, 423 insertions, 427 deletions
diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs
index f0399e89..0940183c 100644
--- a/crates/shirabe/src/package/loader/array_loader.rs
+++ b/crates/shirabe/src/package/loader/array_loader.rs
@@ -39,156 +39,7 @@ impl ArrayLoader {
load_options,
}
}
-}
-
-enum CompleteOrRootPackage {
- Complete(CompletePackage),
- Root(RootPackage),
-}
-
-impl CompleteOrRootPackage {
- fn package(&self) -> &Package {
- match self {
- Self::Complete(p) => &p.inner,
- Self::Root(p) => &p.inner.inner,
- }
- }
-
- fn package_mut(&mut self) -> &mut Package {
- match self {
- Self::Complete(p) => &mut p.inner,
- Self::Root(p) => &mut p.inner.inner,
- }
- }
-
- fn complete_mut(&mut self) -> &mut dyn CompletePackageInterface {
- match self {
- Self::Complete(p) => p,
- Self::Root(p) => p,
- }
- }
-
- fn is_root(&self) -> bool {
- matches!(self, Self::Root(_))
- }
-
- fn get_name(&self) -> &str {
- self.package().get_name()
- }
-
- fn get_pretty_version(&self) -> &str {
- self.package().get_pretty_version()
- }
-
- fn into_handle(self) -> PackageInterfaceHandle {
- match self {
- Self::Complete(p) => CompletePackageHandle::from_complete_package(p).into(),
- Self::Root(p) => RootPackageHandle::from_root_package(p).into(),
- }
- }
-}
-
-fn php_to_map(value: &PhpMixed) -> IndexMap<String, PhpMixed> {
- match value {
- PhpMixed::Array(m) => m.clone(),
- _ => IndexMap::new(),
- }
-}
-
-fn php_to_string_vec(value: &PhpMixed) -> Vec<String> {
- match value {
- PhpMixed::List(l) => l.iter().map(strval).collect(),
- PhpMixed::Array(m) => m.values().map(strval).collect(),
- _ => Vec::new(),
- }
-}
-
-fn apply_link_setter(package: &mut Package, method: &str, links: IndexMap<String, Link>) {
- if method == Link::TYPE_REQUIRE {
- package.set_requires(links);
- } else if method == Link::TYPE_DEV_REQUIRE {
- package.set_dev_requires(links);
- } else if method == Link::TYPE_CONFLICT {
- package.set_conflicts(links);
- } else if method == Link::TYPE_PROVIDE {
- package.set_provides(links);
- } else if method == Link::TYPE_REPLACE {
- package.set_replaces(links);
- }
-}
-
-fn php_to_mirrors(value: &PhpMixed) -> Vec<Mirror> {
- let entries: Vec<&PhpMixed> = match value {
- PhpMixed::List(l) => l.iter().collect(),
- PhpMixed::Array(m) => m.values().collect(),
- _ => Vec::new(),
- };
- entries
- .into_iter()
- .filter_map(|entry| match entry {
- PhpMixed::Array(m) => Some(Mirror {
- url: m
- .get("url")
- .and_then(|v| v.as_string())
- .unwrap_or("")
- .to_string(),
- preferred: m.get("preferred").is_some_and(|v| v.to_bool()),
- }),
- _ => None,
- })
- .collect()
-}
-
-impl LoaderInterface for ArrayLoader {
- fn as_any(&self) -> &dyn std::any::Any {
- self
- }
-
- fn load(
- &self,
- mut config: IndexMap<String, PhpMixed>,
- class: Option<String>,
- ) -> anyhow::Result<PackageInterfaceHandle> {
- let class = class.unwrap_or_else(|| "Composer\\Package\\CompletePackage".to_string());
-
- if class != "Composer\\Package\\CompletePackage"
- && class != "Composer\\Package\\RootPackage"
- {
- trigger_error(
- "The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.",
- E_USER_DEPRECATED,
- );
- }
-
- let mut package = self.create_object(&config, &class)?;
-
- for (r#type, opts) in SUPPORTED_LINK_TYPES.iter() {
- let entry = config.get(*r#type);
- let entry_is_array = entry
- .map(|v| matches!(v, PhpMixed::Array(_)))
- .unwrap_or(false);
- if entry.is_none() || !entry_is_array {
- continue;
- }
- let links = self.parse_links(
- package.get_name(),
- package.get_pretty_version(),
- opts.method,
- match entry.unwrap() {
- PhpMixed::Array(arr) => arr.clone(),
- _ => IndexMap::new(),
- },
- )?;
- apply_link_setter(package.package_mut(), opts.method, links);
- }
-
- let package = self.configure_object(package, &mut config)?;
-
- Ok(package)
- }
-}
-impl ArrayLoader {
#[tracing::instrument(skip_all)]
pub fn load_packages(
&self,
@@ -938,3 +789,150 @@ impl ArrayLoader {
Ok(None)
}
}
+
+enum CompleteOrRootPackage {
+ Complete(CompletePackage),
+ Root(RootPackage),
+}
+
+impl CompleteOrRootPackage {
+ fn package(&self) -> &Package {
+ match self {
+ Self::Complete(p) => &p.inner,
+ Self::Root(p) => &p.inner.inner,
+ }
+ }
+
+ fn package_mut(&mut self) -> &mut Package {
+ match self {
+ Self::Complete(p) => &mut p.inner,
+ Self::Root(p) => &mut p.inner.inner,
+ }
+ }
+
+ fn complete_mut(&mut self) -> &mut dyn CompletePackageInterface {
+ match self {
+ Self::Complete(p) => p,
+ Self::Root(p) => p,
+ }
+ }
+
+ fn is_root(&self) -> bool {
+ matches!(self, Self::Root(_))
+ }
+
+ fn get_name(&self) -> &str {
+ self.package().get_name()
+ }
+
+ fn get_pretty_version(&self) -> &str {
+ self.package().get_pretty_version()
+ }
+
+ fn into_handle(self) -> PackageInterfaceHandle {
+ match self {
+ Self::Complete(p) => CompletePackageHandle::from_complete_package(p).into(),
+ Self::Root(p) => RootPackageHandle::from_root_package(p).into(),
+ }
+ }
+}
+
+fn php_to_map(value: &PhpMixed) -> IndexMap<String, PhpMixed> {
+ match value {
+ PhpMixed::Array(m) => m.clone(),
+ _ => IndexMap::new(),
+ }
+}
+
+fn php_to_string_vec(value: &PhpMixed) -> Vec<String> {
+ match value {
+ PhpMixed::List(l) => l.iter().map(strval).collect(),
+ PhpMixed::Array(m) => m.values().map(strval).collect(),
+ _ => Vec::new(),
+ }
+}
+
+fn apply_link_setter(package: &mut Package, method: &str, links: IndexMap<String, Link>) {
+ if method == Link::TYPE_REQUIRE {
+ package.set_requires(links);
+ } else if method == Link::TYPE_DEV_REQUIRE {
+ package.set_dev_requires(links);
+ } else if method == Link::TYPE_CONFLICT {
+ package.set_conflicts(links);
+ } else if method == Link::TYPE_PROVIDE {
+ package.set_provides(links);
+ } else if method == Link::TYPE_REPLACE {
+ package.set_replaces(links);
+ }
+}
+
+fn php_to_mirrors(value: &PhpMixed) -> Vec<Mirror> {
+ let entries: Vec<&PhpMixed> = match value {
+ PhpMixed::List(l) => l.iter().collect(),
+ PhpMixed::Array(m) => m.values().collect(),
+ _ => Vec::new(),
+ };
+ entries
+ .into_iter()
+ .filter_map(|entry| match entry {
+ PhpMixed::Array(m) => Some(Mirror {
+ url: m
+ .get("url")
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string(),
+ preferred: m.get("preferred").is_some_and(|v| v.to_bool()),
+ }),
+ _ => None,
+ })
+ .collect()
+}
+
+impl LoaderInterface for ArrayLoader {
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+
+ fn load(
+ &self,
+ mut config: IndexMap<String, PhpMixed>,
+ class: Option<String>,
+ ) -> anyhow::Result<PackageInterfaceHandle> {
+ let class = class.unwrap_or_else(|| "Composer\\Package\\CompletePackage".to_string());
+
+ if class != "Composer\\Package\\CompletePackage"
+ && class != "Composer\\Package\\RootPackage"
+ {
+ trigger_error(
+ "The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.",
+ E_USER_DEPRECATED,
+ );
+ }
+
+ let mut package = self.create_object(&config, &class)?;
+
+ for (r#type, opts) in SUPPORTED_LINK_TYPES.iter() {
+ let entry = config.get(*r#type);
+ let entry_is_array = entry
+ .map(|v| matches!(v, PhpMixed::Array(_)))
+ .unwrap_or(false);
+ if entry.is_none() || !entry_is_array {
+ continue;
+ }
+ let links = self.parse_links(
+ package.get_name(),
+ package.get_pretty_version(),
+ opts.method,
+ match entry.unwrap() {
+ PhpMixed::Array(arr) => arr.clone(),
+ _ => IndexMap::new(),
+ },
+ )?;
+ apply_link_setter(package.package_mut(), opts.method, links);
+ }
+
+ let package = self.configure_object(package, &mut config)?;
+
+ Ok(package)
+ }
+}
diff --git a/crates/shirabe/src/package/loader/validating_array_loader.rs b/crates/shirabe/src/package/loader/validating_array_loader.rs
index 0be0ef38..a28df595 100644
--- a/crates/shirabe/src/package/loader/validating_array_loader.rs
+++ b/crates/shirabe/src/package/loader/validating_array_loader.rs
@@ -60,6 +60,282 @@ impl ValidatingArrayLoader {
flags,
}
}
+
+ pub fn get_warnings(&self) -> Vec<String> {
+ self.warnings.borrow().clone()
+ }
+
+ pub fn get_errors(&self) -> Vec<String> {
+ self.errors.borrow().clone()
+ }
+
+ pub fn has_package_naming_error(name: &str, is_link: bool) -> Option<String> {
+ if PlatformRepository::is_platform_package(name) {
+ return None;
+ }
+
+ if !Preg::is_match(
+ php_regex!(
+ "{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD"
+ ),
+ name,
+ ) {
+ return Some(format!(
+ "{} is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match \"^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{{0,2}})[a-z0-9]+)*$\".",
+ name
+ ));
+ }
+
+ let reserved_names = [
+ "nul", "con", "prn", "aux", "com1", "com2", "com3", "com4", "com5", "com6", "com7",
+ "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
+ ];
+ let lower = strtolower(name);
+ let bits: Vec<&str> = lower.split('/').collect();
+ if reserved_names.contains(&bits[0]) || reserved_names.contains(&bits[1]) {
+ return Some(format!(
+ "{} is reserved, package and vendor names can not match any of: {}.",
+ name,
+ reserved_names.join(", ")
+ ));
+ }
+
+ if Preg::is_match(php_regex!("{\\.json$}"), name) {
+ return Some(format!(
+ "{} is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.",
+ name
+ ));
+ }
+
+ if Preg::is_match(php_regex!("{[A-Z]}"), name) {
+ if is_link {
+ return Some(format!(
+ "{} is invalid, it should not contain uppercase characters. Please use {} instead.",
+ name,
+ strtolower(name)
+ ));
+ }
+
+ let suggest_name = Preg::replace(
+ php_regex!("{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}"),
+ "\\1\\3-\\2\\4",
+ name,
+ );
+ let suggest_name = strtolower(&suggest_name);
+
+ return Some(format!(
+ "{} is invalid, it should not contain uppercase characters. We suggest using {} instead.",
+ name, suggest_name
+ ));
+ }
+
+ None
+ }
+
+ fn validate_regex(&self, property: &str, regex: &str, mandatory: bool) -> bool {
+ if !self.validate_string(property, mandatory) {
+ return false;
+ }
+
+ let value = self.config.borrow()[property]
+ .as_string()
+ .unwrap_or("")
+ .to_string();
+ if !Preg::is_match(format!("{{^{}$}}u", regex), &value) {
+ let message = format!(
+ "{} : invalid value ({}), must match {}",
+ property, value, regex
+ );
+ if mandatory {
+ self.errors.borrow_mut().push(message);
+ } else {
+ self.warnings.borrow_mut().push(message);
+ }
+ self.config.borrow_mut().shift_remove(property);
+
+ return false;
+ }
+
+ true
+ }
+
+ fn validate_string(&self, property: &str, mandatory: bool) -> bool {
+ if self.config.borrow().contains_key(property)
+ && !is_string(&self.config.borrow()[property])
+ {
+ self.errors.borrow_mut().push(format!(
+ "{} : should be a string, {} given",
+ property,
+ get_debug_type(&self.config.borrow()[property])
+ ));
+ self.config.borrow_mut().shift_remove(property);
+
+ return false;
+ }
+
+ let is_empty = !self.config.borrow().contains_key(property)
+ || trim(
+ self.config.borrow()[property].as_string().unwrap_or(""),
+ Some(" \t\n\r\0\u{0B}"),
+ )
+ .is_empty();
+ if is_empty {
+ if mandatory {
+ self.errors
+ .borrow_mut()
+ .push(format!("{} : must be present", property));
+ }
+ self.config.borrow_mut().shift_remove(property);
+
+ return false;
+ }
+
+ true
+ }
+
+ fn validate_array(&self, property: &str, mandatory: bool) -> bool {
+ if self.config.borrow().contains_key(property) && !is_array(&self.config.borrow()[property])
+ {
+ self.errors.borrow_mut().push(format!(
+ "{} : should be an array, {} given",
+ property,
+ get_debug_type(&self.config.borrow()[property])
+ ));
+ self.config.borrow_mut().shift_remove(property);
+
+ return false;
+ }
+
+ let is_empty = !self.config.borrow().contains_key(property)
+ || match &self.config.borrow()[property] {
+ PhpMixed::Array(m) => m.is_empty(),
+ PhpMixed::List(l) => l.is_empty(),
+ // is_array() above guarantees the value is Array or List here.
+ _ => unreachable!("validate_array: non-array value survived the is_array check"),
+ };
+ if is_empty {
+ if mandatory {
+ self.errors.borrow_mut().push(format!(
+ "{} : must be present and contain at least one element",
+ property
+ ));
+ }
+ self.config.borrow_mut().shift_remove(property);
+
+ return false;
+ }
+
+ true
+ }
+
+ fn validate_flat_array(&self, property: &str, regex: Option<&str>, mandatory: bool) -> bool {
+ if !self.validate_array(property, mandatory) {
+ return false;
+ }
+
+ let mut pass = true;
+ let entries: Vec<(String, PhpMixed)> = self.config.borrow()[property]
+ .as_array()
+ .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
+ .unwrap_or_default();
+ for (key, value) in entries {
+ if !is_string(&value) && !is_numeric(&value) {
+ self.errors.borrow_mut().push(format!(
+ "{}.{} : must be a string or int, {} given",
+ property,
+ key,
+ get_debug_type(&value)
+ ));
+ if let Some(PhpMixed::Array(arr)) = self.config.borrow_mut().get_mut(property) {
+ arr.shift_remove(&key);
+ }
+ pass = false;
+
+ continue;
+ }
+
+ if let Some(regex_str) = regex {
+ let value_str = php_to_string(&value);
+ if !Preg::is_match(format!("{{^{}$}}u", regex_str), &value_str) {
+ self.warnings.borrow_mut().push(format!(
+ "{}.{} : invalid value ({}), must match {}",
+ property, key, value_str, regex_str
+ ));
+ if let Some(PhpMixed::Array(arr)) = self.config.borrow_mut().get_mut(property) {
+ arr.shift_remove(&key);
+ }
+ pass = false;
+ }
+ }
+ }
+
+ pass
+ }
+
+ fn validate_url(&self, property: &str, mandatory: bool) -> bool {
+ if !self.validate_string(property, mandatory) {
+ return false;
+ }
+
+ let value = self.config.borrow()[property]
+ .as_string()
+ .unwrap_or("")
+ .to_string();
+ if !self.filter_url(&value, &["http", "https"]) {
+ self.warnings.borrow_mut().push(format!(
+ "{} : invalid value ({}), must be an http/https URL",
+ property, value
+ ));
+ self.config.borrow_mut().shift_remove(property);
+
+ return false;
+ }
+
+ true
+ }
+
+ fn filter_url(&self, value: &str, schemes: &[&str]) -> bool {
+ if value.is_empty() {
+ return true;
+ }
+
+ let bits = parse_url_all(value);
+ let bits_map = match bits {
+ PhpMixed::Array(m) => m,
+ _ => return false,
+ };
+ let scheme = bits_map
+ .get("scheme")
+ .and_then(|v| v.as_string())
+ .unwrap_or("");
+ let host = bits_map
+ .get("host")
+ .and_then(|v| v.as_string())
+ .unwrap_or("");
+ if scheme.is_empty() || host.is_empty() {
+ return false;
+ }
+
+ if !schemes.contains(&scheme) {
+ return false;
+ }
+
+ true
+ }
+
+ fn is_empty_array(val: Option<&PhpMixed>) -> bool {
+ match val {
+ Some(v) => match v {
+ PhpMixed::Array(m) => m.is_empty(),
+ PhpMixed::Null => true,
+ PhpMixed::Bool(false) => true,
+ PhpMixed::String(s) => s.is_empty(),
+ PhpMixed::Int(0) => true,
+ _ => false,
+ },
+ None => true,
+ }
+ }
}
impl LoaderInterface for ValidatingArrayLoader {
@@ -1327,281 +1603,3 @@ impl LoaderInterface for ValidatingArrayLoader {
Ok(package)
}
}
-
-impl ValidatingArrayLoader {
- pub fn get_warnings(&self) -> Vec<String> {
- self.warnings.borrow().clone()
- }
-
- pub fn get_errors(&self) -> Vec<String> {
- self.errors.borrow().clone()
- }
-
- pub fn has_package_naming_error(name: &str, is_link: bool) -> Option<String> {
- if PlatformRepository::is_platform_package(name) {
- return None;
- }
-
- if !Preg::is_match(
- php_regex!(
- "{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD"
- ),
- name,
- ) {
- return Some(format!(
- "{} is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match \"^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{{0,2}})[a-z0-9]+)*$\".",
- name
- ));
- }
-
- let reserved_names = [
- "nul", "con", "prn", "aux", "com1", "com2", "com3", "com4", "com5", "com6", "com7",
- "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
- ];
- let lower = strtolower(name);
- let bits: Vec<&str> = lower.split('/').collect();
- if reserved_names.contains(&bits[0]) || reserved_names.contains(&bits[1]) {
- return Some(format!(
- "{} is reserved, package and vendor names can not match any of: {}.",
- name,
- reserved_names.join(", ")
- ));
- }
-
- if Preg::is_match(php_regex!("{\\.json$}"), name) {
- return Some(format!(
- "{} is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.",
- name
- ));
- }
-
- if Preg::is_match(php_regex!("{[A-Z]}"), name) {
- if is_link {
- return Some(format!(
- "{} is invalid, it should not contain uppercase characters. Please use {} instead.",
- name,
- strtolower(name)
- ));
- }
-
- let suggest_name = Preg::replace(
- php_regex!("{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}"),
- "\\1\\3-\\2\\4",
- name,
- );
- let suggest_name = strtolower(&suggest_name);
-
- return Some(format!(
- "{} is invalid, it should not contain uppercase characters. We suggest using {} instead.",
- name, suggest_name
- ));
- }
-
- None
- }
-
- fn validate_regex(&self, property: &str, regex: &str, mandatory: bool) -> bool {
- if !self.validate_string(property, mandatory) {
- return false;
- }
-
- let value = self.config.borrow()[property]
- .as_string()
- .unwrap_or("")
- .to_string();
- if !Preg::is_match(format!("{{^{}$}}u", regex), &value) {
- let message = format!(
- "{} : invalid value ({}), must match {}",
- property, value, regex
- );
- if mandatory {
- self.errors.borrow_mut().push(message);
- } else {
- self.warnings.borrow_mut().push(message);
- }
- self.config.borrow_mut().shift_remove(property);
-
- return false;
- }
-
- true
- }
-
- fn validate_string(&self, property: &str, mandatory: bool) -> bool {
- if self.config.borrow().contains_key(property)
- && !is_string(&self.config.borrow()[property])
- {
- self.errors.borrow_mut().push(format!(
- "{} : should be a string, {} given",
- property,
- get_debug_type(&self.config.borrow()[property])
- ));
- self.config.borrow_mut().shift_remove(property);
-
- return false;
- }
-
- let is_empty = !self.config.borrow().contains_key(property)
- || trim(
- self.config.borrow()[property].as_string().unwrap_or(""),
- Some(" \t\n\r\0\u{0B}"),
- )
- .is_empty();
- if is_empty {
- if mandatory {
- self.errors
- .borrow_mut()
- .push(format!("{} : must be present", property));
- }
- self.config.borrow_mut().shift_remove(property);
-
- return false;
- }
-
- true
- }
-
- fn validate_array(&self, property: &str, mandatory: bool) -> bool {
- if self.config.borrow().contains_key(property) && !is_array(&self.config.borrow()[property])
- {
- self.errors.borrow_mut().push(format!(
- "{} : should be an array, {} given",
- property,
- get_debug_type(&self.config.borrow()[property])
- ));
- self.config.borrow_mut().shift_remove(property);
-
- return false;
- }
-
- let is_empty = !self.config.borrow().contains_key(property)
- || match &self.config.borrow()[property] {
- PhpMixed::Array(m) => m.is_empty(),
- PhpMixed::List(l) => l.is_empty(),
- // is_array() above guarantees the value is Array or List here.
- _ => unreachable!("validate_array: non-array value survived the is_array check"),
- };
- if is_empty {
- if mandatory {
- self.errors.borrow_mut().push(format!(
- "{} : must be present and contain at least one element",
- property
- ));
- }
- self.config.borrow_mut().shift_remove(property);
-
- return false;
- }
-
- true
- }
-
- fn validate_flat_array(&self, property: &str, regex: Option<&str>, mandatory: bool) -> bool {
- if !self.validate_array(property, mandatory) {
- return false;
- }
-
- let mut pass = true;
- let entries: Vec<(String, PhpMixed)> = self.config.borrow()[property]
- .as_array()
- .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
- .unwrap_or_default();
- for (key, value) in entries {
- if !is_string(&value) && !is_numeric(&value) {
- self.errors.borrow_mut().push(format!(
- "{}.{} : must be a string or int, {} given",
- property,
- key,
- get_debug_type(&value)
- ));
- if let Some(PhpMixed::Array(arr)) = self.config.borrow_mut().get_mut(property) {
- arr.shift_remove(&key);
- }
- pass = false;
-
- continue;
- }
-
- if let Some(regex_str) = regex {
- let value_str = php_to_string(&value);
- if !Preg::is_match(format!("{{^{}$}}u", regex_str), &value_str) {
- self.warnings.borrow_mut().push(format!(
- "{}.{} : invalid value ({}), must match {}",
- property, key, value_str, regex_str
- ));
- if let Some(PhpMixed::Array(arr)) = self.config.borrow_mut().get_mut(property) {
- arr.shift_remove(&key);
- }
- pass = false;
- }
- }
- }
-
- pass
- }
-
- fn validate_url(&self, property: &str, mandatory: bool) -> bool {
- if !self.validate_string(property, mandatory) {
- return false;
- }
-
- let value = self.config.borrow()[property]
- .as_string()
- .unwrap_or("")
- .to_string();
- if !self.filter_url(&value, &["http", "https"]) {
- self.warnings.borrow_mut().push(format!(
- "{} : invalid value ({}), must be an http/https URL",
- property, value
- ));
- self.config.borrow_mut().shift_remove(property);
-
- return false;
- }
-
- true
- }
-
- fn filter_url(&self, value: &str, schemes: &[&str]) -> bool {
- if value.is_empty() {
- return true;
- }
-
- let bits = parse_url_all(value);
- let bits_map = match bits {
- PhpMixed::Array(m) => m,
- _ => return false,
- };
- let scheme = bits_map
- .get("scheme")
- .and_then(|v| v.as_string())
- .unwrap_or("");
- let host = bits_map
- .get("host")
- .and_then(|v| v.as_string())
- .unwrap_or("");
- if scheme.is_empty() || host.is_empty() {
- return false;
- }
-
- if !schemes.contains(&scheme) {
- return false;
- }
-
- true
- }
-
- fn is_empty_array(val: Option<&PhpMixed>) -> bool {
- match val {
- Some(v) => match v {
- PhpMixed::Array(m) => m.is_empty(),
- PhpMixed::Null => true,
- PhpMixed::Bool(false) => true,
- PhpMixed::String(s) => s.is_empty(),
- PhpMixed::Int(0) => true,
- _ => false,
- },
- None => true,
- }
- }
-}