1use std::fmt;
2use std::marker::PhantomData;
3
4const MAX_RESOURCES: usize = 7;
5
6#[derive(Clone, Copy, Eq, Hash, PartialEq)]
8pub struct ResourceKind<const PROFILE: u8>(u8);
9
10impl<const PROFILE: u8> ResourceKind<PROFILE> {
11 pub(crate) const fn new(index: u8) -> Self {
12 Self(index)
13 }
14}
15
16impl<const PROFILE: u8> fmt::Debug for ResourceKind<PROFILE> {
17 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18 formatter.write_str(resource_name(PROFILE, self.0))
19 }
20}
21
22const fn operation_name(profile: u8) -> &'static str {
23 match profile {
24 0 => "filesystem project reading",
25 1 => "filesystem Package Tree reading",
26 2 => "filesystem Font Catalog reading",
27 3 => "compilation",
28 4 => "Pack Archive Encode",
29 5 => "Pack Archive Decode",
30 6 => "Package Archive Expansion",
31 7 => "Pack Archive Read",
32 8 => "OpenDAL recursive read",
33 9 => "OpenDAL Project Read",
34 10 => "OpenDAL Font Read",
35 11 => "OpenDAL Package Tree Read",
36 12 => "OpenDAL Package Archive Read",
37 13 => "OpenDAL Package Read",
38 _ => "resource-limited operation",
39 }
40}
41
42const fn resource_name(profile: u8, index: u8) -> &'static str {
43 match (profile, index) {
44 (0, 0) | (1, 0) | (2, 0) => "VisitedEntries",
45 (0, 1) | (1, 1) => "SelectedFiles",
46 (0, 2) => "RootPolicyBytes",
47 (0, 3) | (1, 2) => "SelectedFileBytes",
48 (0, 4) => "TotalSelectedBytes",
49 (1, 3) => "PackageTreeBytes",
50 (2, 1) => "AcceptedContainers",
51 (2, 2) | (10, 4) => "ContainerBytes",
52 (2, 3) => "TotalAcceptedBytes",
53 (3, 0) => "SourcePages",
54 (3, 1) => "Artifacts",
55 (3, 2) => "PixelsPerArtifact",
56 (3, 3) => "TotalPixels",
57 (3, 4) => "ArtifactBytes",
58 (3, 5) => "RetainedArtifactBytes",
59 (3, 6) => "ExportWorkers",
60 (4, 0) | (5, 0) | (7, 0) | (12, 0) => "ArchiveBytes",
61 (4, 1) | (5, 1) | (6, 1) => "Members",
62 (4, 2) => "GeneratedMemberNameBytes",
63 (4, 3) | (5, 3) => "ManifestBytes",
64 (4, 4) | (5, 4) | (6, 3) => "MemberBytes",
65 (4, 5) | (5, 5) => "TotalContentBytes",
66 (5, 2) => "RawMemberNameBytes",
67 (6, 0) => "CompressedArchiveBytes",
68 (6, 2) => "MemberNameBytes",
69 (6, 4) => "TotalExpandedBytes",
70 (8, 0) | (9, 0) | (10, 0) | (11, 0) => "ListedEntries",
71 (8, 1) | (9, 1) | (10, 1) | (11, 1) => "ListedPathBytes",
72 (8, 2) | (9, 2) | (10, 2) | (11, 2) => "TotalListedPathBytes",
73 (8, 3) => "SelectedObjects",
74 (8, 4) | (9, 4) | (11, 4) => "ObjectBytes",
75 (8, 5) | (9, 5) | (10, 5) | (11, 5) => "TotalBytes",
76 (9, 3) | (11, 3) => "SelectedFiles",
77 (10, 3) => "SelectedContainers",
78 (13, 0) => "TreeListedEntries",
79 (13, 1) => "TreeListedPathBytes",
80 (13, 2) => "TreeTotalListedPathBytes",
81 (13, 3) => "TreeSelectedFiles",
82 (13, 4) => "TreeObjectBytes",
83 (13, 5) => "TreeTotalBytes",
84 (13, 6) => "ArchiveBytes",
85 _ => "UnknownResource",
86 }
87}
88
89pub trait Resource: Copy + fmt::Debug + Eq + 'static {
91 const OPERATION: &'static str;
93
94 #[doc(hidden)]
95 fn index(self) -> usize;
96}
97
98impl<const PROFILE: u8> Resource for ResourceKind<PROFILE> {
99 const OPERATION: &'static str = operation_name(PROFILE);
100
101 fn index(self) -> usize {
102 self.0 as usize
103 }
104}
105
106#[derive(Clone, Copy, Eq, PartialEq)]
108pub struct Limits<R: Resource> {
109 pub(crate) ceilings: [u64; MAX_RESOURCES],
110 resource: PhantomData<R>,
111}
112
113impl<R: Resource> Limits<R> {
114 pub(crate) const fn from_ceilings(ceilings: [u64; MAX_RESOURCES]) -> Self {
115 Self {
116 ceilings,
117 resource: PhantomData,
118 }
119 }
120
121 #[track_caller]
122 pub(crate) fn assert_probe_resources(self, resources: impl IntoIterator<Item = R>) -> Self {
123 for resource in resources {
124 let ceiling = self.ceiling(resource);
125 assert_ne!(
126 ceiling,
127 u64::MAX,
128 "the {resource:?} ceiling must leave room for a plus-one probe"
129 );
130 }
131 self
132 }
133
134 pub fn ceiling(&self, resource: R) -> u64 {
136 self.ceilings[resource.index()]
137 }
138}
139
140impl<R: Resource> fmt::Debug for Limits<R> {
141 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142 formatter
143 .debug_struct("Limits")
144 .field("ceilings", &self.ceilings)
145 .finish()
146 }
147}
148
149#[derive(Debug, Clone, Copy, Eq, PartialEq)]
151#[non_exhaustive]
152pub enum LimitError<R: Resource> {
153 Exceeded {
154 resource: R,
155 ceiling: u64,
156 observed_at_least: u64,
157 },
158 AccountingOverflow {
159 resource: R,
160 },
161}
162
163impl<R: Resource> LimitError<R> {
164 pub(crate) fn exceeded(resource: R, ceiling: u64) -> Self {
165 match ceiling.checked_add(1) {
166 Some(observed_at_least) => Self::Exceeded {
167 resource,
168 ceiling,
169 observed_at_least,
170 },
171 None => Self::AccountingOverflow { resource },
172 }
173 }
174}
175
176impl<R: Resource> fmt::Display for LimitError<R> {
177 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178 match self {
179 Self::Exceeded {
180 resource,
181 ceiling,
182 observed_at_least,
183 } => write!(
184 formatter,
185 "{} {resource:?} limit exceeded: ceiling {ceiling}, observed at least {observed_at_least}",
186 R::OPERATION
187 ),
188 Self::AccountingOverflow { resource } => {
189 write!(
190 formatter,
191 "{} {resource:?} accounting overflowed",
192 R::OPERATION
193 )
194 }
195 }
196 }
197}
198
199impl<R: Resource> std::error::Error for LimitError<R> {}