Skip to content

Commit dfea2f0

Browse files
committed
resource_id
1 parent 3ce701f commit dfea2f0

File tree

3 files changed

+91
-82
lines changed

3 files changed

+91
-82
lines changed

lib/bencher_valid/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub use plus::{
5353
CardBrand, CardCvc, CardNumber, Entitlements, ExpirationMonth, ExpirationYear, LastFour,
5454
LicensedPlanId, MeteredPlanId, PlanLevel, PlanStatus,
5555
};
56-
pub use resource_id::{ResourceId, ResourceIdKind};
56+
pub use resource_id::ResourceId;
5757
pub use resource_name::ResourceName;
5858
pub use search::Search;
5959
pub use secret::Secret;

lib/bencher_valid/src/name_id.rs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
use std::{
2-
borrow::Cow,
3-
fmt::{self, Display},
4-
marker::PhantomData,
5-
str::FromStr,
6-
};
1+
use std::{fmt, marker::PhantomData, str::FromStr};
72

3+
use derive_more::Display;
84
#[cfg(feature = "schema")]
95
use schemars::JsonSchema;
106
use serde::{
@@ -14,7 +10,7 @@ use serde::{
1410

1511
use crate::ValidError;
1612

17-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
13+
#[derive(Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
1814
#[serde(untagged)]
1915
pub enum NameId<U, S, T> {
2016
Uuid(U),
@@ -33,8 +29,8 @@ where
3329
"NameId".to_owned()
3430
}
3531

36-
fn schema_id() -> Cow<'static, str> {
37-
Cow::Borrowed("bencher_valid::name_id::NameId")
32+
fn schema_id() -> std::borrow::Cow<'static, str> {
33+
std::borrow::Cow::Borrowed("bencher_valid::name_id::NameId")
3834
}
3935

4036
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::schema::Schema {
@@ -67,21 +63,6 @@ where
6763
}
6864
}
6965

70-
impl<U, S, T> Display for NameId<U, S, T>
71-
where
72-
U: Display,
73-
S: Display,
74-
T: Display,
75-
{
76-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77-
match self {
78-
Self::Uuid(uuid) => uuid.fmt(f),
79-
Self::Slug(slug) => slug.fmt(f),
80-
Self::Name(name) => name.fmt(f),
81-
}
82-
}
83-
}
84-
8566
impl<'de, U, S, T> Deserialize<'de> for NameId<U, S, T>
8667
where
8768
U: FromStr,
Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{fmt, str::FromStr};
1+
use std::{fmt, marker::PhantomData, str::FromStr};
22

33
use derive_more::Display;
44
#[cfg(feature = "schema")]
@@ -7,85 +7,83 @@ use serde::{
77
Deserialize, Serialize,
88
de::{self, Deserializer, Unexpected, Visitor},
99
};
10-
use uuid::Uuid;
1110

12-
use crate::{Slug, ValidError, slug::is_valid_slug};
11+
use crate::ValidError;
1312

1413
#[typeshare::typeshare]
1514
#[derive(Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
16-
#[cfg_attr(feature = "schema", derive(JsonSchema))]
17-
pub struct ResourceId(String);
18-
19-
pub enum ResourceIdKind {
20-
Uuid(Uuid),
21-
Slug(Slug),
15+
#[serde(untagged)]
16+
pub enum ResourceId<U, S> {
17+
Uuid(U),
18+
Slug(S),
2219
}
2320

24-
impl FromStr for ResourceId {
25-
type Err = ValidError;
21+
#[cfg(feature = "schema")]
22+
impl<U, S> JsonSchema for ResourceId<U, S>
23+
where
24+
U: JsonSchema,
25+
S: JsonSchema,
26+
{
27+
fn schema_name() -> String {
28+
"ResourceId".to_owned()
29+
}
2630

27-
fn from_str(value: &str) -> Result<Self, Self::Err> {
28-
// A UUID is always a valid slug
29-
// And a slug is always a valid resource ID
30-
if is_valid_slug(value) {
31-
Ok(Self(value.into()))
32-
} else {
33-
Err(ValidError::ResourceId(value.into()))
34-
}
31+
fn schema_id() -> std::borrow::Cow<'static, str> {
32+
std::borrow::Cow::Borrowed("bencher_valid::resource_id::ResourceId")
33+
}
34+
35+
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::schema::Schema {
36+
// Unfortunately, this seems to be required to have an untagged enum.
37+
// Otherwise, you get a runtime error: `can only flatten structs and maps (got a string)`
38+
// I believe this is a shortcoming of https://github.com/oxidecomputer/progenitor
39+
// For now, we just use the lowest common denominator's schema.
40+
S::json_schema(generator)
3541
}
3642
}
3743

38-
impl TryFrom<&ResourceId> for ResourceIdKind {
39-
type Error = ValidError;
44+
impl<U, S> FromStr for ResourceId<U, S>
45+
where
46+
U: FromStr,
47+
S: FromStr,
48+
{
49+
type Err = ValidError;
4050

41-
fn try_from(resource_id: &ResourceId) -> Result<Self, Self::Error> {
42-
if let Ok(uuid) = Uuid::from_str(resource_id.as_ref()) {
51+
fn from_str(name_id: &str) -> Result<Self, Self::Err> {
52+
if let Ok(uuid) = U::from_str(name_id) {
4353
Ok(Self::Uuid(uuid))
44-
} else if let Ok(slug) = Slug::from_str(resource_id.as_ref()) {
54+
} else if let Ok(slug) = S::from_str(name_id) {
4555
Ok(Self::Slug(slug))
4656
} else {
47-
Err(ValidError::ResourceId(resource_id.as_ref().into()))
57+
Err(ValidError::ResourceId(name_id.to_owned()))
4858
}
4959
}
5060
}
5161

52-
impl From<Uuid> for ResourceId {
53-
fn from(uuid: Uuid) -> Self {
54-
Self(uuid.to_string())
55-
}
56-
}
57-
58-
impl From<Slug> for ResourceId {
59-
fn from(slug: Slug) -> Self {
60-
Self(slug.into())
61-
}
62-
}
63-
64-
impl AsRef<str> for ResourceId {
65-
fn as_ref(&self) -> &str {
66-
&self.0
67-
}
68-
}
69-
70-
impl From<ResourceId> for String {
71-
fn from(resource_id: ResourceId) -> Self {
72-
resource_id.0
73-
}
74-
}
75-
76-
impl<'de> Deserialize<'de> for ResourceId {
77-
fn deserialize<D>(deserializer: D) -> Result<ResourceId, D::Error>
62+
impl<'de, U, S> Deserialize<'de> for ResourceId<U, S>
63+
where
64+
U: FromStr,
65+
S: FromStr,
66+
{
67+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
7868
where
7969
D: Deserializer<'de>,
8070
{
81-
deserializer.deserialize_str(ResourceIdVisitor)
71+
deserializer.deserialize_str(ResourceIdVisitor {
72+
marker: PhantomData,
73+
})
8274
}
8375
}
8476

85-
struct ResourceIdVisitor;
77+
struct ResourceIdVisitor<U, S> {
78+
marker: PhantomData<(U, S)>,
79+
}
8680

87-
impl Visitor<'_> for ResourceIdVisitor {
88-
type Value = ResourceId;
81+
impl<U, S> Visitor<'_> for ResourceIdVisitor<U, S>
82+
where
83+
U: FromStr,
84+
S: FromStr,
85+
{
86+
type Value = ResourceId<U, S>;
8987

9088
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
9189
formatter.write_str("a valid UUID or slug.")
@@ -99,6 +97,36 @@ impl Visitor<'_> for ResourceIdVisitor {
9997
}
10098
}
10199

102-
impl ResourceId {
103-
pub const MAX_LEN: usize = crate::MAX_LEN;
100+
#[cfg(test)]
101+
mod tests {
102+
use super::ResourceId;
103+
use crate::Slug;
104+
use uuid::Uuid;
105+
106+
#[test]
107+
fn test_resource_id_uuid() {
108+
const UUID: &str = "123e4567-e89b-12d3-a456-426614174000";
109+
let resource_id: ResourceId<Uuid, Slug> = UUID.parse().unwrap();
110+
assert_eq!(
111+
resource_id,
112+
ResourceId::Uuid(Uuid::parse_str(UUID).unwrap())
113+
);
114+
115+
let serialized = serde_json::to_string(&resource_id).unwrap();
116+
assert_eq!(serialized, format!("\"{UUID}\""));
117+
let deserialized: ResourceId<Uuid, Slug> = serde_json::from_str(&serialized).unwrap();
118+
assert_eq!(deserialized, resource_id);
119+
}
120+
121+
#[test]
122+
fn test_resource_id_slug() {
123+
const SLUG: &str = "my-slug";
124+
let resource_id: ResourceId<Uuid, Slug> = SLUG.parse().unwrap();
125+
assert_eq!(resource_id, ResourceId::Slug(SLUG.parse().unwrap()));
126+
127+
let serialized = serde_json::to_string(&resource_id).unwrap();
128+
assert_eq!(serialized, format!("\"{SLUG}\""));
129+
let deserialized: ResourceId<Uuid, Slug> = serde_json::from_str(&serialized).unwrap();
130+
assert_eq!(deserialized, resource_id);
131+
}
104132
}

0 commit comments

Comments
 (0)