pub mod comparison;
use cadmpeg_core::decode::DecodeContext;
use cadmpeg_core::CodecError;
use serde::Deserialize;
fn deserialize_local_id<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<String, D::Error> {
let value = String::deserialize(deserializer)?;
if value.is_empty() || value.chars().any(char::is_whitespace) {
Err(serde::de::Error::custom(IdentityError::InvalidId { value }))
} else {
Ok(value)
}
}
use std::fmt::{self, Display};
#[must_use]
pub fn is_valid_identity(id: &str) -> bool {
match check_identity(id, |_| Ok::<(), std::convert::Infallible>(())) {
Ok(valid) => valid,
Err(error) => match error {},
}
}
fn check_identity<E>(id: &str, mut visit: impl FnMut(u64) -> Result<(), E>) -> Result<bool, E> {
visit(0)?;
let mut characters = id.chars();
let mut separators = 0_u8;
let mut key = false;
let mut nonempty = false;
while !characters.as_str().is_empty() {
visit(1)?;
let Some(character) = characters.next() else {
break;
};
if character.is_whitespace() {
return Ok(false);
}
match character {
'#' if key || !nonempty || separators != 2 => return Ok(false),
'#' => {
key = true;
nonempty = false;
}
':' if !key => {
if !nonempty || separators == 2 {
return Ok(false);
}
separators += 1;
nonempty = false;
}
_ => nonempty = true,
}
}
Ok(key && nonempty)
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize)]
#[serde(try_from = "String")]
pub struct Identity(String);
impl serde::Serialize for Identity {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "schema")]
const SCHEMA_WHITESPACE: &str =
r"\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000";
#[cfg(feature = "schema")]
impl schemars::JsonSchema for Identity {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Identity".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
let pattern = format!(
r"^(?:[^:#{SCHEMA_WHITESPACE}]+:){{2}}[^:#{SCHEMA_WHITESPACE}]+#[^#{SCHEMA_WHITESPACE}]+(?![\s\S])"
);
schemars::json_schema!({"type": "string", "pattern": pattern})
}
}
impl Identity {
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
Ok(Self(ctx.copy_retained_text(self.as_str(), operation)?))
}
pub fn new(value: impl Into<String>) -> Result<Self, IdentityError> {
match Self::admit_text(value.into(), |_| Ok::<(), std::convert::Infallible>(())) {
Ok(result) => result.map_err(|value| IdentityError::InvalidId { value }),
Err(error) => match error {},
}
}
pub(crate) fn admit_text<E>(
value: String,
visit: impl FnMut(u64) -> Result<(), E>,
) -> Result<Result<Self, String>, E> {
Ok(if check_identity(&value, visit)? {
Ok(Self(value))
} else {
Err(value)
})
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn key(&self) -> IdentityKey {
IdentityKey(std::borrow::Cow::Owned(self.0.split('#').skip(1).collect()))
}
#[must_use]
pub fn with_kind(&self, kind: &IdentityComponent) -> Self {
let mut value = String::with_capacity(self.0.len() + kind.as_str().len());
for component in self.0.split(':').take(2) {
value.push_str(component);
value.push(':');
}
value.push_str(kind.as_str());
value.push('#');
for key in self.0.split('#').skip(1) {
value.push_str(key);
}
Self(value)
}
#[must_use]
pub fn with_key_tail(mut self, tail: &IdentityKeyTail) -> Self {
self.0.push_str(tail.as_str());
self
}
pub fn try_with_key_tail(
mut self,
tail: &IdentityKeyTail,
) -> Result<Self, std::collections::TryReserveError> {
self.0.try_reserve(tail.as_str().len())?;
self.0.push_str(tail.as_str());
Ok(self)
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl TryFrom<String> for Identity {
type Error = IdentityError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<&str> for Identity {
type Error = IdentityError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl Display for Identity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
const fn decode_scalar(bytes: &[u8], index: usize) -> (u32, usize) {
let first = bytes[index];
if first < 0x80 {
(u32::from_be_bytes([0, 0, 0, first]), 1)
} else if first < 0xe0 {
(
((u32::from_be_bytes([0, 0, 0, first & 0x1f])) << 6)
| (u32::from_be_bytes([0, 0, 0, bytes[index + 1] & 0x3f])),
2,
)
} else if first < 0xf0 {
(
((u32::from_be_bytes([0, 0, 0, first & 0x0f])) << 12)
| ((u32::from_be_bytes([0, 0, 0, bytes[index + 1] & 0x3f])) << 6)
| (u32::from_be_bytes([0, 0, 0, bytes[index + 2] & 0x3f])),
3,
)
} else {
(
((u32::from_be_bytes([0, 0, 0, first & 0x07])) << 18)
| ((u32::from_be_bytes([0, 0, 0, bytes[index + 1] & 0x3f])) << 12)
| ((u32::from_be_bytes([0, 0, 0, bytes[index + 2] & 0x3f])) << 6)
| (u32::from_be_bytes([0, 0, 0, bytes[index + 3] & 0x3f])),
4,
)
}
}
const fn is_unicode_whitespace(codepoint: u32) -> bool {
matches!(
codepoint,
0x0009..=0x000d
| 0x0020
| 0x0085
| 0x00a0
| 0x1680
| 0x2000..=0x200a
| 0x2028
| 0x2029
| 0x202f
| 0x205f
| 0x3000
)
}
const fn contains_unicode_whitespace(value: &str) -> bool {
let bytes = value.as_bytes();
let mut index = 0;
while index < bytes.len() {
let (codepoint, width) = decode_scalar(bytes, index);
if is_unicode_whitespace(codepoint) {
return true;
}
index += width;
}
false
}
const fn valid_component_text(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.is_empty() || contains_unicode_whitespace(value) {
return false;
}
let mut index = 0;
while index < bytes.len() {
if matches!(bytes[index], b':' | b'#') {
return false;
}
index += 1;
}
true
}
const fn valid_key_text(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.is_empty() || contains_unicode_whitespace(value) {
return false;
}
let mut index = 0;
while index < bytes.len() {
if bytes[index] == b'#' {
return false;
}
index += 1;
}
true
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StaticIdentityComponent {
value: &'static str,
}
impl StaticIdentityComponent {
#[must_use]
pub const fn new(value: &'static str) -> Option<Self> {
if valid_component_text(value) {
Some(Self { value })
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IdentityComponent(std::borrow::Cow<'static, str>);
impl IdentityComponent {
pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
let value = value.into();
if valid_component_text(&value) {
Ok(Self(std::borrow::Cow::Owned(value)))
} else {
Err(IdentityError::InvalidComponent {
label: "component",
value,
})
}
}
#[must_use]
pub const fn from_static(proof: StaticIdentityComponent) -> Self {
Self(std::borrow::Cow::Borrowed(proof.value))
}
#[must_use]
pub fn then(self, part: impl Into<Self>) -> Self {
let mut text = self.0.into_owned();
text.push_str(part.into().as_str());
Self(std::borrow::Cow::Owned(text))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
macro_rules! identity_component_from_number {
($($number:ty),* $(,)?) => {$(
impl From<$number> for IdentityComponent {
fn from(value: $number) -> Self {
Self(std::borrow::Cow::Owned(value.to_string()))
}
}
)*};
}
identity_component_from_number!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
impl From<&IdentityComponent> for IdentityComponent {
fn from(value: &IdentityComponent) -> Self {
value.clone()
}
}
impl TryFrom<String> for IdentityComponent {
type Error = IdentityError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl TryFrom<&str> for IdentityComponent {
type Error = IdentityError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::try_new(value.to_owned())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StaticIdentityKey {
value: &'static str,
}
impl StaticIdentityKey {
#[must_use]
pub const fn new(value: &'static str) -> Option<Self> {
if valid_key_text(value) {
Some(Self { value })
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IdentityKey(std::borrow::Cow<'static, str>);
rewrite_scalar!(IdentityKey);
impl IdentityKey {
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
Ok(Self(match &self.0 {
std::borrow::Cow::Borrowed(text) => {
ctx.charge_work(1, operation)?;
std::borrow::Cow::Borrowed(*text)
}
std::borrow::Cow::Owned(text) => {
std::borrow::Cow::Owned(ctx.copy_retained_text(text, operation)?)
}
}))
}
#[must_use]
pub fn encode_segment(value: &str) -> Self {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
if value.is_empty() {
return Self(std::borrow::Cow::Owned("%EMPTY".to_owned()));
}
let mut encoded = String::with_capacity(value.len());
for byte in value.bytes() {
if byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b'/') {
encoded.push(char::from(byte));
} else {
encoded.push('%');
encoded.push(char::from(HEX[usize::from(byte >> 4)]));
encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
}
Self(std::borrow::Cow::Owned(encoded))
}
#[must_use]
pub fn encode_key_text(value: &str) -> Self {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
if value.is_empty() {
return Self(std::borrow::Cow::Owned("%EMPTY".to_owned()));
}
let mut encoded = String::with_capacity(value.len());
for character in value.chars() {
if character == '%' || character == '#' || character.is_whitespace() {
let mut buffer = [0u8; 4];
for byte in character.encode_utf8(&mut buffer).as_bytes() {
encoded.push('%');
encoded.push(char::from(HEX[usize::from(byte >> 4)]));
encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
} else {
encoded.push(character);
}
}
Self(std::borrow::Cow::Owned(encoded))
}
#[must_use]
pub fn to_ascii_lowercase(&self) -> Self {
Self(std::borrow::Cow::Owned(self.0.to_ascii_lowercase()))
}
#[must_use]
pub fn into_string(self) -> String {
self.0.into_owned()
}
#[must_use]
pub fn zero_padded(value: u64, width: usize) -> Self {
Self(std::borrow::Cow::Owned(format!("{value:0width$}")))
}
#[must_use]
pub fn hex_byte(value: u8) -> Self {
let mut text = String::with_capacity(2);
append_hex_bytes(&mut text, &[value]);
Self(std::borrow::Cow::Owned(text))
}
#[must_use]
pub fn with_hex_bytes(self, bytes: &[u8]) -> Self {
let mut text = self.0.into_owned();
append_hex_bytes(&mut text, bytes);
Self(std::borrow::Cow::Owned(text))
}
pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
let value = value.into();
if valid_key_text(&value) {
Ok(Self(std::borrow::Cow::Owned(value)))
} else {
Err(IdentityError::InvalidKey { value })
}
}
#[must_use]
pub const fn from_static(proof: StaticIdentityKey) -> Self {
Self(std::borrow::Cow::Borrowed(proof.value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
fn append_hex_bytes(text: &mut String, bytes: &[u8]) {
const DIGITS: &[u8; 16] = b"0123456789abcdef";
for byte in bytes {
text.push(char::from(DIGITS[usize::from(byte >> 4)]));
text.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
}
}
impl TryFrom<String> for IdentityKey {
type Error = IdentityError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl TryFrom<&str> for IdentityKey {
type Error = IdentityError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::try_new(value.to_owned())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StaticIdentityNamespace {
format: &'static str,
scope: &'static str,
kind: &'static str,
}
impl StaticIdentityNamespace {
#[must_use]
pub const fn new(
format: &'static str,
scope: &'static str,
kind: &'static str,
) -> Option<Self> {
if valid_component_text(format) && valid_component_text(scope) && valid_component_text(kind)
{
Some(Self {
format,
scope,
kind,
})
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IdentityNamespace {
format: IdentityComponent,
scope: IdentityComponent,
kind: IdentityComponent,
}
impl IdentityNamespace {
pub fn new(
format: impl Into<String>,
scope: impl Into<String>,
kind: impl Into<String>,
) -> Result<Self, IdentityError> {
let format = format.into();
let scope = scope.into();
let kind = kind.into();
let format_component = Self::component("format", format)?;
let scope_component = Self::component("scope", scope)?;
let kind_component = Self::component("kind", kind)?;
Ok(Self {
format: format_component,
scope: scope_component,
kind: kind_component,
})
}
#[must_use]
pub const fn from_static(proof: StaticIdentityNamespace) -> Self {
Self {
format: IdentityComponent(std::borrow::Cow::Borrowed(proof.format)),
scope: IdentityComponent(std::borrow::Cow::Borrowed(proof.scope)),
kind: IdentityComponent(std::borrow::Cow::Borrowed(proof.kind)),
}
}
#[must_use]
pub fn from_components(
format: &IdentityComponent,
scope: &IdentityComponent,
kind: &IdentityComponent,
) -> Self {
Self {
format: format.clone(),
scope: scope.clone(),
kind: kind.clone(),
}
}
fn component(label: &'static str, value: String) -> Result<IdentityComponent, IdentityError> {
if valid_component_text(&value) {
Ok(IdentityComponent(std::borrow::Cow::Owned(value)))
} else {
Err(IdentityError::InvalidComponent { label, value })
}
}
#[must_use]
pub fn format(&self) -> &str {
self.format.as_str()
}
#[must_use]
pub fn scope(&self) -> &str {
self.scope.as_str()
}
#[must_use]
pub fn kind(&self) -> &str {
self.kind.as_str()
}
}
#[macro_export]
macro_rules! identity_namespace {
($format:literal, $scope:literal, $kind:literal $(,)?) => {{
const STATIC_IDENTITY_NAMESPACE: $crate::ids::StaticIdentityNamespace =
match $crate::ids::StaticIdentityNamespace::new($format, $scope, $kind) {
Some(namespace) => namespace,
None => panic!("identity namespace literal has invalid grammar"),
};
$crate::ids::IdentityNamespace::from_static(STATIC_IDENTITY_NAMESPACE)
}};
}
#[macro_export]
macro_rules! identity_component {
($value:literal) => {{
const STATIC_IDENTITY_COMPONENT: $crate::ids::StaticIdentityComponent =
match $crate::ids::StaticIdentityComponent::new($value) {
Some(component) => component,
None => panic!("identity component literal has invalid grammar"),
};
$crate::ids::IdentityComponent::from_static(STATIC_IDENTITY_COMPONENT)
}};
}
#[macro_export]
macro_rules! identity_key {
($value:literal) => {{
const STATIC_IDENTITY_KEY: $crate::ids::StaticIdentityKey =
match $crate::ids::StaticIdentityKey::new($value) {
Some(key) => key,
None => panic!("identity key literal has invalid grammar"),
};
$crate::ids::IdentityKey::from_static(STATIC_IDENTITY_KEY)
}};
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct IdentityKeyTail(String);
impl IdentityKeyTail {
pub fn try_new(value: impl Into<String>) -> Result<Self, IdentityError> {
let value = value.into();
if value.is_empty() || valid_key_text(&value) {
Ok(Self(value))
} else {
Err(IdentityError::InvalidKey { value })
}
}
#[must_use]
pub fn percent_encode(value: &str) -> Self {
let mut encoded = String::with_capacity(value.len());
for character in value.chars() {
if matches!(character, ':' | '#' | '%') || character.is_whitespace() {
let mut bytes = [0; 4];
for byte in character.encode_utf8(&mut bytes).as_bytes() {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
encoded.push('%');
encoded.push(char::from(HEX[usize::from(byte >> 4)]));
encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
} else {
encoded.push(character);
}
}
Self(encoded)
}
#[must_use]
pub fn empty() -> Self {
Self(String::new())
}
#[must_use]
pub fn dash(mut self, part: impl Into<IdentityKey>) -> Self {
self.0.push('-');
self.0.push_str(part.into().as_str());
self
}
#[must_use]
pub fn then(mut self, part: impl Into<IdentityKey>) -> Self {
self.0.push_str(part.into().as_str());
self
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl IdentityKey {
#[must_use]
pub fn dash(self, part: impl Into<IdentityKey>) -> Self {
let mut text = self.0.into_owned();
text.push('-');
text.push_str(part.into().as_str());
Self(std::borrow::Cow::Owned(text))
}
#[must_use]
pub fn colon(self, part: impl Into<IdentityKey>) -> Self {
let mut text = self.0.into_owned();
text.push(':');
text.push_str(part.into().as_str());
Self(std::borrow::Cow::Owned(text))
}
#[must_use]
pub fn then(self, part: impl Into<IdentityKey>) -> Self {
let mut text = self.0.into_owned();
text.push_str(part.into().as_str());
Self(std::borrow::Cow::Owned(text))
}
#[must_use]
pub fn with_tail(self, tail: &IdentityKeyTail) -> Self {
let mut text = self.0.into_owned();
text.push_str(tail.as_str());
Self(std::borrow::Cow::Owned(text))
}
#[must_use]
pub fn with_prefix(self, prefix: &IdentityKeyTail) -> Self {
let mut text = String::with_capacity(prefix.as_str().len() + self.0.len());
text.push_str(prefix.as_str());
text.push_str(self.as_str());
Self(std::borrow::Cow::Owned(text))
}
}
macro_rules! identity_key_from_number {
($($number:ty),* $(,)?) => {$(
impl From<$number> for IdentityKey {
fn from(value: $number) -> Self {
Self(std::borrow::Cow::Owned(value.to_string()))
}
}
impl From<&$number> for IdentityKey {
fn from(value: &$number) -> Self {
Self(std::borrow::Cow::Owned(value.to_string()))
}
}
impl From<&&$number> for IdentityKey {
fn from(value: &&$number) -> Self {
Self(std::borrow::Cow::Owned(value.to_string()))
}
}
)*};
}
identity_key_from_number!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
impl From<&IdentityKey> for IdentityKey {
fn from(value: &IdentityKey) -> Self {
value.clone()
}
}
impl Display for IdentityKeyTail {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Display for IdentityComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Display for IdentityKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Identity {
#[must_use]
pub fn compose(namespace: &IdentityNamespace, key: impl Into<IdentityKey>) -> Self {
let key = key.into();
let mut value = String::with_capacity(
namespace.format().len()
+ namespace.scope().len()
+ namespace.kind().len()
+ key.as_str().len()
+ 4,
);
value.push_str(namespace.format());
value.push(':');
value.push_str(namespace.scope());
value.push(':');
value.push_str(namespace.kind());
value.push('#');
value.push_str(key.as_str());
Self(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IdentityError {
InvalidComponent {
label: &'static str,
value: String,
},
InvalidKey {
value: String,
},
InvalidId {
value: String,
},
}
impl fmt::Display for IdentityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidComponent { label, value } => {
write!(f, "identity {label} component is invalid: {value:?}")
}
Self::InvalidKey { value } => write!(f, "identity key is invalid: {value:?}"),
Self::InvalidId { value } => write!(f, "identity is invalid: {value:?}"),
}
}
}
impl std::error::Error for IdentityError {}
macro_rules! id_type {
($(#[$meta:meta])* $name:ident $(, $helper:ident)*) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(transparent)]
pub struct $name($crate::ids::Identity);
impl $crate::schema::rewrite::typed::RewriteIdentities for $name {
fn rewrite_native_value<F: FnMut(&str) -> Result<String, cadmpeg_core::CodecError>>(ctx: &cadmpeg_core::decode::DecodeContext<'_>, value: &mut serde_json::Value, map: &mut $crate::schema::rewrite::typed::IdentityMap<'_, F>) -> Result<(), cadmpeg_core::CodecError> {
<$crate::ids::Identity as $crate::schema::rewrite::typed::RewriteIdentities>::rewrite_native_value(ctx, value, map)
}
fn visit_identity_references(&self, ctx: &cadmpeg_core::decode::DecodeContext<'_>, visitor: &mut dyn FnMut(&str) -> Result<(), cadmpeg_core::CodecError>) -> Result<(), cadmpeg_core::CodecError> {
$crate::schema::rewrite::typed::RewriteIdentities::visit_identity_references(&self.0, ctx, visitor)
}
fn rewrite_identities<F>(self, ctx: &cadmpeg_core::decode::DecodeContext<'_>, map: &mut $crate::schema::rewrite::typed::IdentityMap<'_, F>) -> Result<Self, cadmpeg_core::CodecError>
where F: FnMut(&str) -> Result<String, cadmpeg_core::CodecError> {
$crate::schema::rewrite::typed::RewriteIdentities::rewrite_identities(self.0, ctx, map).map(Self)
}
}
impl serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.0.as_str())
}
}
impl $name {
pub fn try_clone_for_decode(
&self,
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, cadmpeg_core::CodecError> {
Ok(Self(self.0.try_clone_for_decode(ctx, operation)?))
}
pub fn mint(value: impl Into<String>) -> Result<Self, $crate::ids::IdentityError> {
$crate::ids::Identity::new(value).map(Self::from)
}
$($crate::ids::id_type!(@helper $helper);)*
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.as_str())
}
}
impl From<$name> for $crate::ids::Identity {
fn from(value: $name) -> Self { value.0 }
}
impl From<$crate::ids::Identity> for $name {
fn from(value: $crate::ids::Identity) -> Self { Self(value) }
}
impl TryFrom<String> for $name {
type Error = $crate::ids::IdentityError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::mint(value)
}
}
impl TryFrom<&str> for $name {
type Error = $crate::ids::IdentityError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::mint(value)
}
}
};
(@helper compose) => {
#[must_use]
pub fn compose(
namespace: &$crate::ids::IdentityNamespace,
key: impl Into<$crate::ids::IdentityKey>,
) -> Self {
Self($crate::ids::Identity::compose(namespace, key))
}
};
(@helper into_string) => {
#[must_use]
pub fn into_string(self) -> String {
self.0.into_string()
}
};
(@helper key) => {
#[must_use]
pub fn key(&self) -> $crate::ids::IdentityKey {
self.0.key()
}
};
}
pub(crate) use id_type;
macro_rules! local_id_type {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(transparent)]
pub struct $name(#[serde(deserialize_with = "crate::ids::deserialize_local_id")] String);
rewrite_record!($name, []; (text));
#[cfg(feature = "schema")]
impl schemars::JsonSchema for $name {
fn schema_name() -> std::borrow::Cow<'static, str> {
stringify!($name).into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
let pattern = format!(r"^[^{}]+(?![\s\S])", SCHEMA_WHITESPACE);
schemars::json_schema!({"type": "string", "pattern": pattern})
}
}
impl $name {
pub fn try_clone_for_decode(
&self,
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, cadmpeg_core::CodecError> {
Ok(Self(ctx.copy_retained_text(self.as_str(), operation)?))
}
pub fn mint(value: impl Into<String>) -> Result<Self, $crate::ids::IdentityError> {
let value = value.into();
if value.is_empty() || value.chars().any(char::is_whitespace) {
return Err($crate::ids::IdentityError::InvalidId { value });
}
Ok(Self(value))
}
#[must_use]
pub fn compose(
namespace: &$crate::ids::IdentityNamespace,
key: impl Into<$crate::ids::IdentityKey>,
) -> Self {
Self($crate::ids::Identity::compose(namespace, key).into_string())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl TryFrom<String> for $name {
type Error = $crate::ids::IdentityError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::mint(value)
}
}
impl TryFrom<&str> for $name {
type Error = $crate::ids::IdentityError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::mint(value)
}
}
};
}
id_type!(
BodyId, compose, into_string
);
id_type!(
FeatureInputTopologyId, compose
);
id_type!(
FeatureResultTopologyId, compose
);
local_id_type!(
HistoricalBodyId
);
local_id_type!(
HistoricalFaceId
);
local_id_type!(
HistoricalEdgeId
);
local_id_type!(
HistoricalVertexId
);
id_type!(
RegionId, compose
);
id_type!(
ShellId, compose
);
id_type!(
FaceId, compose, into_string, key
);
id_type!(
LoopId, compose, key
);
id_type!(
CoedgeId, compose, key
);
id_type!(
EdgeId, compose, into_string, key
);
id_type!(
VertexId, compose, into_string, key
);
id_type!(
SurfaceId, compose, key
);
id_type!(
CurveId, compose, key
);
id_type!(
PcurveId, compose
);
id_type!(
ProceduralSurfaceId, compose, into_string
);
id_type!(
ProceduralCurveId, compose, into_string
);
id_type!(
SubdId, compose
);
id_type!(
PointId, compose
);
id_type!(
UnknownId, compose, into_string
);
impl std::borrow::Borrow<str> for UnknownId {
fn borrow(&self) -> &str {
self.as_str()
}
}
id_type!(
AppearanceId, compose, key
);
id_type!(
AppearanceBindingId, compose
);
id_type!(
AttributeId, compose, into_string
);
id_type!(
ProductDefinitionId, compose
);
id_type!(
OccurrenceId, compose
);
id_type!(
PmiId, compose, into_string
);
id_type!(
LayerId
);
#[cfg(test)]
mod tests;