1mod package;
125
126pub use package::*;
127
128use std::fmt;
129
130use super::read::recursive::{
131 RecursiveReadLimits, RecursiveReadOperation, RecursiveReadResource, RecursiveReadSelection,
132 RecursiveSurveyIssue, RecursiveSurveyIssueKind, read_recursive_prefix, read_recursive_prefixes,
133};
134use super::{BoxError, Location, LocationRoleError, OperatorResolver};
135use crate::FontDisposition;
136use crate::limits::{LimitError, Limits, ResourceKind};
137use crate::redacted_error::RedactedError;
138
139fn aggregate_issue_message<T: fmt::Display>(issues: &[T], summary: &str) -> String {
140 if let [issue] = issues {
141 issue.to_string()
142 } else {
143 format!("{summary} with {} issue(s)", issues.len())
144 }
145}
146
147fn failed_path_context(path: Option<&str>) -> String {
148 path.map(|path| format!(" while reading object operation path {path:?}"))
149 .unwrap_or_default()
150}
151
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
154pub struct ProjectReadCeilings {
155 pub listed_entries: u64,
156 pub listed_path_bytes: u64,
157 pub total_listed_path_bytes: u64,
158 pub selected_files: u64,
159 pub object_bytes: u64,
160 pub total_bytes: u64,
161}
162
163impl ProjectReadCeilings {
164 pub const fn reference_v1() -> Self {
166 Self {
167 listed_entries: 1_000_000,
168 listed_path_bytes: 64 * 1024,
169 total_listed_path_bytes: 256 * 1024 * 1024,
170 selected_files: 100_000,
171 object_bytes: 256 * 1024 * 1024,
172 total_bytes: 2 * 1024 * 1024 * 1024,
173 }
174 }
175}
176
177pub type ProjectReadResource = ResourceKind<9>;
179
180#[allow(non_upper_case_globals)]
181impl ResourceKind<9> {
182 pub const ListedEntries: Self = Self::new(0);
183 pub const ListedPathBytes: Self = Self::new(1);
184 pub const TotalListedPathBytes: Self = Self::new(2);
185 pub const SelectedFiles: Self = Self::new(3);
186 pub const ObjectBytes: Self = Self::new(4);
187 pub const TotalBytes: Self = Self::new(5);
188}
189
190pub type ProjectReadLimits = Limits<ProjectReadResource>;
192
193impl Limits<ProjectReadResource> {
194 #[track_caller]
196 pub fn new(ceilings: ProjectReadCeilings) -> Self {
197 let limits = Self::from_ceilings([
198 ceilings.listed_entries,
199 ceilings.listed_path_bytes,
200 ceilings.total_listed_path_bytes,
201 ceilings.selected_files,
202 ceilings.object_bytes,
203 ceilings.total_bytes,
204 0,
205 ])
206 .assert_probe_resources([
207 ProjectReadResource::ListedEntries,
208 ProjectReadResource::ListedPathBytes,
209 ProjectReadResource::TotalListedPathBytes,
210 ProjectReadResource::SelectedFiles,
211 ProjectReadResource::ObjectBytes,
212 ProjectReadResource::TotalBytes,
213 ]);
214 assert!(
215 ceilings.object_bytes <= ceilings.total_bytes,
216 "the ObjectBytes ceiling {} exceeds the TotalBytes ceiling {}",
217 ceilings.object_bytes,
218 ceilings.total_bytes
219 );
220 limits
221 }
222
223 pub const fn reference_v1() -> Self {
225 Self::from_ceilings([
226 1_000_000,
227 64 * 1024,
228 256 * 1024 * 1024,
229 100_000,
230 256 * 1024 * 1024,
231 2 * 1024 * 1024 * 1024,
232 0,
233 ])
234 }
235
236 pub const fn listed_entries(&self) -> u64 {
237 self.ceilings[0]
238 }
239
240 pub const fn listed_path_bytes(&self) -> u64 {
241 self.ceilings[1]
242 }
243
244 pub const fn total_listed_path_bytes(&self) -> u64 {
245 self.ceilings[2]
246 }
247
248 pub const fn selected_files(&self) -> u64 {
249 self.ceilings[3]
250 }
251
252 pub const fn object_bytes(&self) -> u64 {
253 self.ceilings[4]
254 }
255
256 pub const fn total_bytes(&self) -> u64 {
257 self.ceilings[5]
258 }
259}
260
261pub type ProjectReadLimitError = LimitError<ProjectReadResource>;
263
264#[derive(Clone, Debug)]
266pub struct ProjectReadRequest {
267 source: Location,
268 limits: ProjectReadLimits,
269}
270
271impl ProjectReadRequest {
272 pub fn new(
274 source: Location,
275 limits: ProjectReadLimits,
276 ) -> Result<Self, ProjectReadRequestError> {
277 if let Err(role_error) = source.require_prefix() {
278 return Err(ProjectReadRequestError::InvalidSourceRole {
279 location: source,
280 source: role_error,
281 });
282 }
283 Ok(Self { source, limits })
284 }
285
286 pub fn source(&self) -> &Location {
288 &self.source
289 }
290
291 pub const fn limits(&self) -> ProjectReadLimits {
293 self.limits
294 }
295}
296
297#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
299#[non_exhaustive]
300pub enum ProjectReadRequestError {
301 #[error("project source {location} is not a prefix: {source}")]
302 InvalidSourceRole {
303 location: Location,
304 #[source]
305 source: LocationRoleError,
306 },
307}
308
309pub struct ProjectReadEntry {
311 relative_path: String,
312 bytes: Vec<u8>,
313}
314
315impl ProjectReadEntry {
316 pub fn relative_path(&self) -> &str {
318 &self.relative_path
319 }
320
321 pub fn bytes(&self) -> &[u8] {
323 &self.bytes
324 }
325
326 pub fn len(&self) -> u64 {
328 self.bytes.len() as u64
329 }
330
331 pub fn is_empty(&self) -> bool {
333 self.bytes.is_empty()
334 }
335
336 pub fn into_parts(self) -> (String, Vec<u8>) {
338 (self.relative_path, self.bytes)
339 }
340}
341
342impl fmt::Debug for ProjectReadEntry {
343 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
344 formatter
345 .debug_struct("ProjectReadEntry")
346 .field("relative_path", &self.relative_path)
347 .field("byte_length", &self.bytes.len())
348 .finish()
349 }
350}
351
352pub struct ProjectRead {
354 source: Location,
355 entries: Vec<ProjectReadEntry>,
356}
357
358impl ProjectRead {
359 pub fn source(&self) -> &Location {
361 &self.source
362 }
363
364 pub fn entries(&self) -> &[ProjectReadEntry] {
366 &self.entries
367 }
368
369 pub fn into_parts(self) -> (Location, Vec<ProjectReadEntry>) {
371 (self.source, self.entries)
372 }
373}
374
375impl fmt::Debug for ProjectRead {
376 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
377 formatter
378 .debug_struct("ProjectRead")
379 .field("source", &self.source)
380 .field("entries", &self.entries)
381 .finish()
382 }
383}
384
385#[derive(Clone, Copy, Debug, Eq, PartialEq)]
387#[non_exhaustive]
388pub enum ProjectReadEntryKind {
389 Unknown,
390}
391
392#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
394#[non_exhaustive]
395pub enum ProjectReadIssue {
396 #[error("listed operation path {operation_path:?} is outside the project prefix")]
397 ListedPathOutsidePrefix { operation_path: String },
398 #[error("listed operation path {operation_path:?} is a prefix marker where a file is required")]
399 PrefixMarkerWhereFileRequired { operation_path: String },
400 #[error("listed operation path {operation_path:?} has an empty relative path")]
401 EmptyRelativeOperationPath { operation_path: String },
402 #[error("listed operation path {operation_path:?} is not a valid relative operation path")]
403 InvalidRelativeOperationPath { operation_path: String },
404 #[error("listed object {operation_path:?} was yielded more than once")]
405 DuplicateListedObject { operation_path: String },
406 #[error("listed operation path {operation_path:?} has unsupported kind {kind:?}")]
407 UnsupportedEntryKind {
408 operation_path: String,
409 kind: ProjectReadEntryKind,
410 },
411}
412
413#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
415#[error(
416 "{message}",
417 message = aggregate_issue_message(.issues.as_slice(), "project survey failed")
418)]
419pub struct ProjectReadSurveyError {
420 issues: Vec<ProjectReadIssue>,
421}
422
423impl ProjectReadSurveyError {
424 pub fn issues(&self) -> &[ProjectReadIssue] {
426 &self.issues
427 }
428}
429
430#[allow(clippy::result_large_err)]
478pub async fn read_project<R: OperatorResolver + ?Sized>(
479 resolver: &R,
480 request: &ProjectReadRequest,
481) -> Result<ProjectRead, ProjectReadError> {
482 let source = request.source().clone();
483 let entries = read_recursive_prefix(
484 resolver,
485 request.source(),
486 RecursiveReadSelection::AllFiles,
487 request.limits().into(),
488 &ProjectReadOperation {
489 source_location: request.source(),
490 },
491 )
492 .await?
493 .into_iter()
494 .map(|object| ProjectReadEntry {
495 relative_path: object.relative_path,
496 bytes: object.bytes,
497 })
498 .collect();
499
500 Ok(ProjectRead { source, entries })
501}
502
503#[derive(Debug, thiserror::Error)]
508#[error(
509 "Project Read failed for binding {binding} at prefix operation path {operation_path:?}{failed_path}: {cause}",
510 binding = .source_location.binding(),
511 operation_path = .source_location.operation_path(),
512 failed_path = failed_path_context(.failed_path.as_deref()),
513)]
514pub struct ProjectReadError {
515 source_location: Location,
516 failed_path: Option<String>,
517 #[source]
518 cause: RedactedError<ProjectReadErrorCause>,
519}
520
521impl ProjectReadError {
522 pub fn source_location(&self) -> &Location {
524 &self.source_location
525 }
526
527 pub fn failed_path(&self) -> Option<&str> {
529 self.failed_path.as_deref()
530 }
531
532 pub fn cause(&self) -> &ProjectReadErrorCause {
534 self.cause.inner()
535 }
536
537 fn new(
538 source_location: &Location,
539 failed_path: Option<String>,
540 cause: ProjectReadErrorCause,
541 ) -> Self {
542 Self {
543 source_location: source_location.clone(),
544 failed_path,
545 cause: RedactedError::new(cause),
546 }
547 }
548}
549
550#[derive(Debug, thiserror::Error)]
552#[non_exhaustive]
553pub enum ProjectReadErrorCause {
554 #[error("operator resolution failed")]
555 ResolveOperator(#[source] BoxError),
556 #[error("required listing or read capability is unsupported")]
557 UnsupportedCapabilities {
558 list: bool,
559 list_with_recursive: bool,
560 read: bool,
561 },
562 #[error("the recursive listing failed")]
563 List(#[source] ::opendal::Error),
564 #[error("a listed object read failed")]
565 Read(#[source] ::opendal::Error),
566 #[error("a listed object was absent when read")]
567 ListedObjectAbsent(#[source] ::opendal::Error),
568 #[error("the completed listing had structural issues")]
569 Structural(#[source] ProjectReadSurveyError),
570 #[error("a Project Read limit failed")]
571 Limit(#[source] ProjectReadLimitError),
572}
573
574struct ProjectReadOperation<'a> {
575 source_location: &'a Location,
576}
577
578impl RecursiveReadOperation for ProjectReadOperation<'_> {
579 type Error = ProjectReadError;
580
581 fn invalid_location_role(&self, _: usize, _: LocationRoleError) -> ProjectReadError {
582 unreachable!("ProjectReadRequest validates the prefix role")
583 }
584
585 fn resolve_operator(&self, _: usize, source: BoxError) -> ProjectReadError {
586 ProjectReadError::new(
587 self.source_location,
588 None,
589 ProjectReadErrorCause::ResolveOperator(source),
590 )
591 }
592
593 fn unsupported_capabilities(
594 &self,
595 _: usize,
596 list: bool,
597 list_with_recursive: bool,
598 read: bool,
599 ) -> ProjectReadError {
600 ProjectReadError::new(
601 self.source_location,
602 None,
603 ProjectReadErrorCause::UnsupportedCapabilities {
604 list,
605 list_with_recursive,
606 read,
607 },
608 )
609 }
610
611 fn list(&self, _: usize, source: ::opendal::Error) -> ProjectReadError {
612 ProjectReadError::new(
613 self.source_location,
614 None,
615 ProjectReadErrorCause::List(source),
616 )
617 }
618
619 fn read(&self, _: usize, operation_path: String, source: ::opendal::Error) -> ProjectReadError {
620 ProjectReadError::new(
621 self.source_location,
622 Some(operation_path),
623 ProjectReadErrorCause::Read(source),
624 )
625 }
626
627 fn listed_object_absent(
628 &self,
629 _: usize,
630 operation_path: String,
631 source: ::opendal::Error,
632 ) -> ProjectReadError {
633 ProjectReadError::new(
634 self.source_location,
635 Some(operation_path),
636 ProjectReadErrorCause::ListedObjectAbsent(source),
637 )
638 }
639
640 fn structural(&self, _: usize, issues: Vec<RecursiveSurveyIssue>) -> ProjectReadError {
641 ProjectReadError::new(
642 self.source_location,
643 None,
644 ProjectReadErrorCause::Structural(ProjectReadSurveyError {
645 issues: issues.into_iter().map(map_issue).collect(),
646 }),
647 )
648 }
649
650 fn limit(
651 &self,
652 _: usize,
653 resource: RecursiveReadResource,
654 ceiling: u64,
655 _: u64,
656 ) -> ProjectReadError {
657 ProjectReadError::new(
658 self.source_location,
659 None,
660 ProjectReadErrorCause::Limit(ProjectReadLimitError::exceeded(
661 map_resource(resource),
662 ceiling,
663 )),
664 )
665 }
666
667 fn accounting_overflow(&self, _: usize, resource: RecursiveReadResource) -> ProjectReadError {
668 ProjectReadError::new(
669 self.source_location,
670 None,
671 ProjectReadErrorCause::Limit(ProjectReadLimitError::AccountingOverflow {
672 resource: map_resource(resource),
673 }),
674 )
675 }
676}
677
678impl From<ProjectReadLimits> for RecursiveReadLimits {
679 fn from(limits: ProjectReadLimits) -> Self {
680 Self::new(
681 limits.listed_entries(),
682 limits.listed_path_bytes(),
683 limits.total_listed_path_bytes(),
684 limits.selected_files(),
685 limits.object_bytes(),
686 limits.total_bytes(),
687 )
688 }
689}
690
691fn map_resource(resource: RecursiveReadResource) -> ProjectReadResource {
692 match resource {
693 RecursiveReadResource::ListedEntries => ProjectReadResource::ListedEntries,
694 RecursiveReadResource::ListedPathBytes => ProjectReadResource::ListedPathBytes,
695 RecursiveReadResource::TotalListedPathBytes => ProjectReadResource::TotalListedPathBytes,
696 RecursiveReadResource::SelectedObjects => ProjectReadResource::SelectedFiles,
697 RecursiveReadResource::ObjectBytes => ProjectReadResource::ObjectBytes,
698 RecursiveReadResource::TotalBytes => ProjectReadResource::TotalBytes,
699 _ => unreachable!("unknown recursive read resource"),
700 }
701}
702
703fn map_issue(issue: RecursiveSurveyIssue) -> ProjectReadIssue {
704 let operation_path = issue.operation_path;
705 match issue.kind {
706 RecursiveSurveyIssueKind::ListedPathOutsidePrefix => {
707 ProjectReadIssue::ListedPathOutsidePrefix { operation_path }
708 }
709 RecursiveSurveyIssueKind::PrefixMarkerWhereFileRequired => {
710 ProjectReadIssue::PrefixMarkerWhereFileRequired { operation_path }
711 }
712 RecursiveSurveyIssueKind::EmptyRelativeOperationPath => {
713 ProjectReadIssue::EmptyRelativeOperationPath { operation_path }
714 }
715 RecursiveSurveyIssueKind::InvalidRelativeOperationPath => {
716 ProjectReadIssue::InvalidRelativeOperationPath { operation_path }
717 }
718 RecursiveSurveyIssueKind::DuplicateListedObject => {
719 ProjectReadIssue::DuplicateListedObject { operation_path }
720 }
721 RecursiveSurveyIssueKind::UnsupportedEntryKind => ProjectReadIssue::UnsupportedEntryKind {
722 operation_path,
723 kind: ProjectReadEntryKind::Unknown,
724 },
725 }
726}
727
728#[derive(Clone, Copy, Debug, Eq, PartialEq)]
730pub struct FontReadCeilings {
731 pub listed_entries: u64,
732 pub listed_path_bytes: u64,
733 pub total_listed_path_bytes: u64,
734 pub selected_containers: u64,
735 pub container_bytes: u64,
736 pub total_bytes: u64,
737}
738
739impl FontReadCeilings {
740 pub const fn reference_v1() -> Self {
742 Self {
743 listed_entries: 100_000,
744 listed_path_bytes: 64 * 1024,
745 total_listed_path_bytes: 64 * 1024 * 1024,
746 selected_containers: 16_384,
747 container_bytes: 256 * 1024 * 1024,
748 total_bytes: 2 * 1024 * 1024 * 1024,
749 }
750 }
751}
752
753pub type FontReadResource = ResourceKind<10>;
755
756#[allow(non_upper_case_globals)]
757impl ResourceKind<10> {
758 pub const ListedEntries: Self = Self::new(0);
759 pub const ListedPathBytes: Self = Self::new(1);
760 pub const TotalListedPathBytes: Self = Self::new(2);
761 pub const SelectedContainers: Self = Self::new(3);
762 pub const ContainerBytes: Self = Self::new(4);
763 pub const TotalBytes: Self = Self::new(5);
764}
765
766pub type FontReadLimits = Limits<FontReadResource>;
768
769impl Limits<FontReadResource> {
770 #[track_caller]
772 pub fn new(ceilings: FontReadCeilings) -> Self {
773 let limits = Self::from_ceilings([
774 ceilings.listed_entries,
775 ceilings.listed_path_bytes,
776 ceilings.total_listed_path_bytes,
777 ceilings.selected_containers,
778 ceilings.container_bytes,
779 ceilings.total_bytes,
780 0,
781 ])
782 .assert_probe_resources([
783 FontReadResource::ListedEntries,
784 FontReadResource::ListedPathBytes,
785 FontReadResource::TotalListedPathBytes,
786 FontReadResource::SelectedContainers,
787 FontReadResource::ContainerBytes,
788 FontReadResource::TotalBytes,
789 ]);
790 assert!(
791 ceilings.container_bytes <= ceilings.total_bytes,
792 "the ContainerBytes ceiling {} exceeds the TotalBytes ceiling {}",
793 ceilings.container_bytes,
794 ceilings.total_bytes
795 );
796 limits
797 }
798
799 pub const fn reference_v1() -> Self {
801 Self::from_ceilings([
802 100_000,
803 64 * 1024,
804 64 * 1024 * 1024,
805 16_384,
806 256 * 1024 * 1024,
807 2 * 1024 * 1024 * 1024,
808 0,
809 ])
810 }
811
812 pub const fn listed_entries(&self) -> u64 {
813 self.ceilings[0]
814 }
815
816 pub const fn listed_path_bytes(&self) -> u64 {
817 self.ceilings[1]
818 }
819
820 pub const fn total_listed_path_bytes(&self) -> u64 {
821 self.ceilings[2]
822 }
823
824 pub const fn selected_containers(&self) -> u64 {
825 self.ceilings[3]
826 }
827
828 pub const fn container_bytes(&self) -> u64 {
829 self.ceilings[4]
830 }
831
832 pub const fn total_bytes(&self) -> u64 {
833 self.ceilings[5]
834 }
835}
836
837pub type FontReadLimitError = LimitError<FontReadResource>;
839
840#[derive(Clone, Debug, Eq, PartialEq)]
842pub struct FontSource {
843 source: Location,
844 disposition: FontDisposition,
845}
846
847impl FontSource {
848 pub fn new(source: Location, disposition: FontDisposition) -> Self {
850 Self {
851 source,
852 disposition,
853 }
854 }
855
856 pub fn source(&self) -> &Location {
858 &self.source
859 }
860
861 pub const fn disposition(&self) -> FontDisposition {
863 self.disposition
864 }
865}
866
867#[derive(Clone, Debug)]
869pub struct FontReadRequest {
870 sources: Vec<FontSource>,
871 limits: FontReadLimits,
872}
873
874impl FontReadRequest {
875 pub fn new(
877 sources: impl IntoIterator<Item = FontSource>,
878 limits: FontReadLimits,
879 ) -> Result<Self, FontReadRequestRejection> {
880 let sources = sources.into_iter().collect::<Vec<_>>();
881 let issues = sources
882 .iter()
883 .enumerate()
884 .filter_map(|(source_index, configured)| {
885 configured.source.require_prefix().err().map(|source| {
886 FontReadRequestIssue::InvalidSourceRole {
887 source_index,
888 location: configured.source.clone(),
889 source,
890 }
891 })
892 })
893 .collect::<Vec<_>>();
894 if !issues.is_empty() {
895 return Err(FontReadRequestRejection { issues });
896 }
897 Ok(Self { sources, limits })
898 }
899
900 pub fn sources(&self) -> &[FontSource] {
902 &self.sources
903 }
904
905 pub const fn limits(&self) -> FontReadLimits {
907 self.limits
908 }
909}
910
911#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
913#[error(
914 "{message}",
915 message = aggregate_issue_message(.issues.as_slice(), "Font Read request rejected")
916)]
917pub struct FontReadRequestRejection {
918 issues: Vec<FontReadRequestIssue>,
919}
920
921impl FontReadRequestRejection {
922 pub fn issues(&self) -> &[FontReadRequestIssue] {
924 &self.issues
925 }
926}
927
928#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
930#[non_exhaustive]
931pub enum FontReadRequestIssue {
932 #[error("font source {source_index} at {location} is not a prefix: {source}")]
933 InvalidSourceRole {
934 source_index: usize,
935 location: Location,
936 #[source]
937 source: LocationRoleError,
938 },
939}
940
941pub struct FontReadEntry {
943 source_index: usize,
944 source: Location,
945 relative_path: String,
946 disposition: FontDisposition,
947 bytes: Vec<u8>,
948}
949
950impl FontReadEntry {
951 pub fn source_index(&self) -> usize {
953 self.source_index
954 }
955
956 pub fn source(&self) -> &Location {
958 &self.source
959 }
960
961 pub fn relative_path(&self) -> &str {
963 &self.relative_path
964 }
965
966 pub const fn disposition(&self) -> FontDisposition {
968 self.disposition
969 }
970
971 pub fn bytes(&self) -> &[u8] {
973 &self.bytes
974 }
975
976 pub fn len(&self) -> u64 {
978 self.bytes.len() as u64
979 }
980
981 pub fn is_empty(&self) -> bool {
983 self.bytes.is_empty()
984 }
985
986 pub fn into_parts(self) -> (usize, Location, String, FontDisposition, Vec<u8>) {
988 (
989 self.source_index,
990 self.source,
991 self.relative_path,
992 self.disposition,
993 self.bytes,
994 )
995 }
996}
997
998impl fmt::Debug for FontReadEntry {
999 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1000 formatter
1001 .debug_struct("FontReadEntry")
1002 .field("source_index", &self.source_index)
1003 .field("source", &self.source)
1004 .field("relative_path", &self.relative_path)
1005 .field("disposition", &self.disposition)
1006 .field("byte_length", &self.bytes.len())
1007 .finish()
1008 }
1009}
1010
1011pub struct FontRead {
1013 sources: Vec<FontSource>,
1014 entries: Vec<FontReadEntry>,
1015}
1016
1017impl FontRead {
1018 pub fn sources(&self) -> &[FontSource] {
1020 &self.sources
1021 }
1022
1023 pub fn entries(&self) -> &[FontReadEntry] {
1025 &self.entries
1026 }
1027
1028 pub fn into_parts(self) -> (Vec<FontSource>, Vec<FontReadEntry>) {
1030 (self.sources, self.entries)
1031 }
1032}
1033
1034impl fmt::Debug for FontRead {
1035 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1036 formatter
1037 .debug_struct("FontRead")
1038 .field("sources", &self.sources)
1039 .field("entries", &self.entries)
1040 .finish()
1041 }
1042}
1043
1044#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1046#[non_exhaustive]
1047pub enum FontReadEntryKind {
1048 Unknown,
1049}
1050
1051#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
1053#[non_exhaustive]
1054pub enum FontReadIssue {
1055 #[error(
1056 "font source {source_index} listed operation path {operation_path:?} outside its prefix"
1057 )]
1058 ListedPathOutsidePrefix {
1059 source_index: usize,
1060 operation_path: String,
1061 },
1062 #[error(
1063 "font source {source_index} listed operation path {operation_path:?} as a prefix marker where a file is required"
1064 )]
1065 PrefixMarkerWhereFileRequired {
1066 source_index: usize,
1067 operation_path: String,
1068 },
1069 #[error(
1070 "font source {source_index} listed operation path {operation_path:?} with an empty relative path"
1071 )]
1072 EmptyRelativeOperationPath {
1073 source_index: usize,
1074 operation_path: String,
1075 },
1076 #[error("font source {source_index} listed invalid relative operation path {operation_path:?}")]
1077 InvalidRelativeOperationPath {
1078 source_index: usize,
1079 operation_path: String,
1080 },
1081 #[error("font source {source_index} listed object {operation_path:?} more than once")]
1082 DuplicateListedObject {
1083 source_index: usize,
1084 operation_path: String,
1085 },
1086 #[error(
1087 "font source {source_index} listed operation path {operation_path:?} with unsupported kind {kind:?}"
1088 )]
1089 UnsupportedEntryKind {
1090 source_index: usize,
1091 operation_path: String,
1092 kind: FontReadEntryKind,
1093 },
1094}
1095
1096#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
1098#[error(
1099 "{message}",
1100 message = aggregate_issue_message(.issues.as_slice(), "font survey failed")
1101)]
1102pub struct FontReadSurveyError {
1103 issues: Vec<FontReadIssue>,
1104}
1105
1106impl FontReadSurveyError {
1107 pub fn issues(&self) -> &[FontReadIssue] {
1109 &self.issues
1110 }
1111}
1112
1113#[allow(clippy::result_large_err)]
1158pub async fn read_fonts<R: OperatorResolver + ?Sized>(
1159 resolver: &R,
1160 request: &FontReadRequest,
1161) -> Result<FontRead, FontReadError> {
1162 let locations = request
1163 .sources()
1164 .iter()
1165 .map(FontSource::source)
1166 .collect::<Vec<_>>();
1167 let read = read_recursive_prefixes(
1168 resolver,
1169 &locations,
1170 RecursiveReadSelection::FontContainers,
1171 request.limits().into(),
1172 &FontReadOperation {
1173 sources: request.sources(),
1174 },
1175 )
1176 .await?;
1177
1178 let sources = request.sources().to_vec();
1179 let entries = read
1180 .into_iter()
1181 .enumerate()
1182 .flat_map(|(source_index, objects)| {
1183 let source = sources[source_index].clone();
1184 objects.into_iter().map(move |object| FontReadEntry {
1185 source_index,
1186 source: source.source.clone(),
1187 relative_path: object.relative_path,
1188 disposition: source.disposition,
1189 bytes: object.bytes,
1190 })
1191 })
1192 .collect();
1193
1194 Ok(FontRead { sources, entries })
1195}
1196
1197#[derive(Debug, thiserror::Error)]
1202#[error(
1203 "Font Read failed at source {source_index} for binding {binding} at prefix operation path {operation_path:?}{failed_path}: {cause}",
1204 binding = .source_location.binding(),
1205 operation_path = .source_location.operation_path(),
1206 failed_path = failed_path_context(.failed_path.as_deref()),
1207)]
1208pub struct FontReadError {
1209 source_index: usize,
1210 source_location: Location,
1211 failed_path: Option<String>,
1212 #[source]
1213 cause: RedactedError<FontReadErrorCause>,
1214}
1215
1216impl FontReadError {
1217 pub fn source_index(&self) -> usize {
1219 self.source_index
1220 }
1221
1222 pub fn source_location(&self) -> &Location {
1224 &self.source_location
1225 }
1226
1227 pub fn failed_path(&self) -> Option<&str> {
1229 self.failed_path.as_deref()
1230 }
1231
1232 pub fn cause(&self) -> &FontReadErrorCause {
1234 self.cause.inner()
1235 }
1236
1237 fn new(
1238 source_index: usize,
1239 source_location: &Location,
1240 failed_path: Option<String>,
1241 cause: FontReadErrorCause,
1242 ) -> Self {
1243 Self {
1244 source_index,
1245 source_location: source_location.clone(),
1246 failed_path,
1247 cause: RedactedError::new(cause),
1248 }
1249 }
1250}
1251
1252#[derive(Debug, thiserror::Error)]
1254#[non_exhaustive]
1255pub enum FontReadErrorCause {
1256 #[error("operator resolution failed")]
1257 ResolveOperator(#[source] BoxError),
1258 #[error("required listing or read capability is unsupported")]
1259 UnsupportedCapabilities {
1260 list: bool,
1261 list_with_recursive: bool,
1262 read: bool,
1263 },
1264 #[error("a recursive listing failed")]
1265 List(#[source] ::opendal::Error),
1266 #[error("a listed Font Container read failed")]
1267 Read(#[source] ::opendal::Error),
1268 #[error("a listed Font Container was absent when read")]
1269 ListedObjectAbsent(#[source] ::opendal::Error),
1270 #[error("the completed listings had structural issues")]
1271 Structural(#[source] FontReadSurveyError),
1272 #[error("a Font Read limit failed")]
1273 Limit(#[source] FontReadLimitError),
1274}
1275
1276struct FontReadOperation<'a> {
1277 sources: &'a [FontSource],
1278}
1279
1280impl FontReadOperation<'_> {
1281 fn error(
1282 &self,
1283 source_index: usize,
1284 failed_path: Option<String>,
1285 cause: FontReadErrorCause,
1286 ) -> FontReadError {
1287 FontReadError::new(
1288 source_index,
1289 self.sources[source_index].source(),
1290 failed_path,
1291 cause,
1292 )
1293 }
1294}
1295
1296impl RecursiveReadOperation for FontReadOperation<'_> {
1297 type Error = FontReadError;
1298
1299 fn invalid_location_role(&self, _: usize, _: LocationRoleError) -> FontReadError {
1300 unreachable!("FontReadRequest validates every prefix role")
1301 }
1302
1303 fn resolve_operator(&self, source_index: usize, source: BoxError) -> FontReadError {
1304 self.error(
1305 source_index,
1306 None,
1307 FontReadErrorCause::ResolveOperator(source),
1308 )
1309 }
1310
1311 fn unsupported_capabilities(
1312 &self,
1313 source_index: usize,
1314 list: bool,
1315 list_with_recursive: bool,
1316 read: bool,
1317 ) -> FontReadError {
1318 self.error(
1319 source_index,
1320 None,
1321 FontReadErrorCause::UnsupportedCapabilities {
1322 list,
1323 list_with_recursive,
1324 read,
1325 },
1326 )
1327 }
1328
1329 fn list(&self, source_index: usize, source: ::opendal::Error) -> FontReadError {
1330 self.error(source_index, None, FontReadErrorCause::List(source))
1331 }
1332
1333 fn read(
1334 &self,
1335 source_index: usize,
1336 operation_path: String,
1337 source: ::opendal::Error,
1338 ) -> FontReadError {
1339 self.error(
1340 source_index,
1341 Some(operation_path),
1342 FontReadErrorCause::Read(source),
1343 )
1344 }
1345
1346 fn listed_object_absent(
1347 &self,
1348 source_index: usize,
1349 operation_path: String,
1350 source: ::opendal::Error,
1351 ) -> FontReadError {
1352 self.error(
1353 source_index,
1354 Some(operation_path),
1355 FontReadErrorCause::ListedObjectAbsent(source),
1356 )
1357 }
1358
1359 fn structural(&self, source_index: usize, issues: Vec<RecursiveSurveyIssue>) -> FontReadError {
1360 self.error(
1361 source_index,
1362 None,
1363 FontReadErrorCause::Structural(FontReadSurveyError {
1364 issues: issues.into_iter().map(map_font_issue).collect(),
1365 }),
1366 )
1367 }
1368
1369 fn limit(
1370 &self,
1371 source_index: usize,
1372 resource: RecursiveReadResource,
1373 ceiling: u64,
1374 _: u64,
1375 ) -> FontReadError {
1376 self.error(
1377 source_index,
1378 None,
1379 FontReadErrorCause::Limit(FontReadLimitError::exceeded(
1380 map_font_resource(resource),
1381 ceiling,
1382 )),
1383 )
1384 }
1385
1386 fn accounting_overflow(
1387 &self,
1388 source_index: usize,
1389 resource: RecursiveReadResource,
1390 ) -> FontReadError {
1391 self.error(
1392 source_index,
1393 None,
1394 FontReadErrorCause::Limit(FontReadLimitError::AccountingOverflow {
1395 resource: map_font_resource(resource),
1396 }),
1397 )
1398 }
1399}
1400
1401impl From<FontReadLimits> for RecursiveReadLimits {
1402 fn from(limits: FontReadLimits) -> Self {
1403 Self::new(
1404 limits.listed_entries(),
1405 limits.listed_path_bytes(),
1406 limits.total_listed_path_bytes(),
1407 limits.selected_containers(),
1408 limits.container_bytes(),
1409 limits.total_bytes(),
1410 )
1411 }
1412}
1413
1414fn map_font_resource(resource: RecursiveReadResource) -> FontReadResource {
1415 match resource {
1416 RecursiveReadResource::ListedEntries => FontReadResource::ListedEntries,
1417 RecursiveReadResource::ListedPathBytes => FontReadResource::ListedPathBytes,
1418 RecursiveReadResource::TotalListedPathBytes => FontReadResource::TotalListedPathBytes,
1419 RecursiveReadResource::SelectedObjects => FontReadResource::SelectedContainers,
1420 RecursiveReadResource::ObjectBytes => FontReadResource::ContainerBytes,
1421 RecursiveReadResource::TotalBytes => FontReadResource::TotalBytes,
1422 _ => unreachable!("unknown recursive read resource"),
1423 }
1424}
1425
1426fn map_font_issue(issue: RecursiveSurveyIssue) -> FontReadIssue {
1427 let source_index = issue.source_index;
1428 let operation_path = issue.operation_path;
1429 match issue.kind {
1430 RecursiveSurveyIssueKind::ListedPathOutsidePrefix => {
1431 FontReadIssue::ListedPathOutsidePrefix {
1432 source_index,
1433 operation_path,
1434 }
1435 }
1436 RecursiveSurveyIssueKind::PrefixMarkerWhereFileRequired => {
1437 FontReadIssue::PrefixMarkerWhereFileRequired {
1438 source_index,
1439 operation_path,
1440 }
1441 }
1442 RecursiveSurveyIssueKind::EmptyRelativeOperationPath => {
1443 FontReadIssue::EmptyRelativeOperationPath {
1444 source_index,
1445 operation_path,
1446 }
1447 }
1448 RecursiveSurveyIssueKind::InvalidRelativeOperationPath => {
1449 FontReadIssue::InvalidRelativeOperationPath {
1450 source_index,
1451 operation_path,
1452 }
1453 }
1454 RecursiveSurveyIssueKind::DuplicateListedObject => FontReadIssue::DuplicateListedObject {
1455 source_index,
1456 operation_path,
1457 },
1458 RecursiveSurveyIssueKind::UnsupportedEntryKind => FontReadIssue::UnsupportedEntryKind {
1459 source_index,
1460 operation_path,
1461 kind: FontReadEntryKind::Unknown,
1462 },
1463 }
1464}