Skip to content

Commit 0d44f1d

Browse files
author
Ronald Holshausen
committed
chore: record reasons for transitions in the state machine
1 parent f3968da commit 0d44f1d

File tree

1 file changed

+99
-80
lines changed

1 file changed

+99
-80
lines changed

src/lib.rs

Lines changed: 99 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -419,25 +419,25 @@ impl Decision {
419419
}
420420

421421
enum Transition {
422-
To(Decision),
423-
Branch(Decision, Decision)
422+
To(Decision),
423+
Branch(Decision, Decision)
424424
}
425425

426426
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
427427
enum DecisionResult {
428-
True,
429-
False,
430-
StatusCode(u16)
428+
True(String),
429+
False(String),
430+
StatusCode(u16)
431431
}
432432

433433
impl DecisionResult {
434-
fn wrap(result: bool) -> DecisionResult {
435-
if result {
436-
DecisionResult::True
437-
} else {
438-
DecisionResult::False
439-
}
434+
fn wrap(result: bool, reason: &str) -> DecisionResult {
435+
if result {
436+
DecisionResult::True(format!("is: {}", reason))
437+
} else {
438+
DecisionResult::False(format!("is not: {}", reason))
440439
}
440+
}
441441
}
442442

443443
lazy_static! {
@@ -552,190 +552,209 @@ fn execute_decision(
552552
Decision::B10MethodAllowed => {
553553
match resource.allowed_methods
554554
.iter().find(|m| m.to_uppercase() == context.request.method.to_uppercase()) {
555-
Some(_) => DecisionResult::True,
555+
Some(_) => DecisionResult::True("method is in the list of allowed methods".to_string()),
556556
None => {
557557
context.response.add_header("Allow", resource.allowed_methods
558558
.iter()
559559
.cloned()
560560
.map(HeaderValue::basic)
561561
.collect());
562-
DecisionResult::False
562+
DecisionResult::False("method is not in the list of allowed methods".to_string())
563563
}
564564
}
565565
},
566566
Decision::B11UriTooLong => {
567567
let callback = resource.uri_too_long.lock().unwrap();
568-
DecisionResult::wrap(callback.deref()(context, resource))
568+
DecisionResult::wrap(callback.deref()(context, resource), "URI too long")
569569
},
570570
Decision::B12KnownMethod => DecisionResult::wrap(resource.known_methods
571-
.iter().find(|m| m.to_uppercase() == context.request.method.to_uppercase()).is_some()),
571+
.iter().find(|m| m.to_uppercase() == context.request.method.to_uppercase()).is_some(),
572+
"known method"),
572573
Decision::B13Available => {
573574
let callback = resource.available.lock().unwrap();
574-
DecisionResult::wrap(callback.deref()(context, resource))
575+
DecisionResult::wrap(callback.deref()(context, resource), "available")
575576
},
576577
Decision::B9MalformedRequest => {
577578
let callback = resource.malformed_request.lock().unwrap();
578-
DecisionResult::wrap(callback.deref()(context, resource))
579+
DecisionResult::wrap(callback.deref()(context, resource), "malformed request")
579580
},
580581
Decision::B8Authorized => {
581582
let callback = resource.not_authorized.lock().unwrap();
582583
match callback.deref()(context, resource) {
583584
Some(realm) => {
584585
context.response.add_header("WWW-Authenticate", vec![HeaderValue::parse_string(realm.as_str())]);
585-
DecisionResult::False
586+
DecisionResult::False("is not authorized".to_string())
586587
},
587-
None => DecisionResult::True
588+
None => DecisionResult::True("is not authorized".to_string())
588589
}
589590
},
590591
Decision::B7Forbidden => {
591592
let callback = resource.forbidden.lock().unwrap();
592-
DecisionResult::wrap(callback.deref()(context, resource))
593+
DecisionResult::wrap(callback.deref()(context, resource), "forbidden")
593594
},
594595
Decision::B6UnsupportedContentHeader => {
595596
let callback = resource.unsupported_content_headers.lock().unwrap();
596-
DecisionResult::wrap(callback.deref()(context, resource))
597+
DecisionResult::wrap(callback.deref()(context, resource), "unsupported content headers")
597598
},
598599
Decision::B5UnknownContentType => {
599600
DecisionResult::wrap(context.request.is_put_or_post() && resource.acceptable_content_types
600601
.iter().find(|ct| context.request.content_type().to_uppercase() == ct.to_uppercase() )
601-
.is_none())
602+
.is_none(), "acceptable content types")
602603
},
603604
Decision::B4RequestEntityTooLarge => {
604605
let callback = resource.valid_entity_length.lock().unwrap();
605-
DecisionResult::wrap(context.request.is_put_or_post() && !callback.deref()(context, resource))
606+
DecisionResult::wrap(context.request.is_put_or_post() && !callback.deref()(context, resource),
607+
"valid entity length")
606608
},
607-
Decision::B3Options => DecisionResult::wrap(context.request.is_options()),
608-
Decision::C3AcceptExists => DecisionResult::wrap(context.request.has_accept_header()),
609+
Decision::B3Options => DecisionResult::wrap(context.request.is_options(), "options"),
610+
Decision::C3AcceptExists => DecisionResult::wrap(context.request.has_accept_header(), "has accept header"),
609611
Decision::C4AcceptableMediaTypeAvailable => match content_negotiation::matching_content_type(resource, &context.request) {
610612
Some(media_type) => {
611613
context.selected_media_type = Some(media_type);
612-
DecisionResult::True
614+
DecisionResult::True("acceptable media type is available".to_string())
613615
},
614-
None => DecisionResult::False
616+
None => DecisionResult::False("acceptable media type is not available".to_string())
615617
},
616-
Decision::D4AcceptLanguageExists => DecisionResult::wrap(context.request.has_accept_language_header()),
618+
Decision::D4AcceptLanguageExists => DecisionResult::wrap(context.request.has_accept_language_header(),
619+
"has accept language header"),
617620
Decision::D5AcceptableLanguageAvailable => match content_negotiation::matching_language(resource, &context.request) {
618621
Some(language) => {
619622
if language != "*" {
620623
context.selected_language = Some(language.clone());
621624
context.response.add_header("Content-Language", vec![HeaderValue::parse_string(&language)]);
622625
}
623-
DecisionResult::True
626+
DecisionResult::True("acceptable language is available".to_string())
624627
},
625-
None => DecisionResult::False
628+
None => DecisionResult::False("acceptable language is not available".to_string())
626629
},
627-
Decision::E5AcceptCharsetExists => DecisionResult::wrap(context.request.has_accept_charset_header()),
630+
Decision::E5AcceptCharsetExists => DecisionResult::wrap(context.request.has_accept_charset_header(),
631+
"accept charset exists"),
628632
Decision::E6AcceptableCharsetAvailable => match content_negotiation::matching_charset(resource, &context.request) {
629633
Some(charset) => {
630634
if charset != "*" {
631635
context.selected_charset = Some(charset.clone());
632636
}
633-
DecisionResult::True
637+
DecisionResult::True("acceptable charset is available".to_string())
634638
},
635-
None => DecisionResult::False
639+
None => DecisionResult::False("acceptable charset is not available".to_string())
636640
},
637-
Decision::F6AcceptEncodingExists => DecisionResult::wrap(context.request.has_accept_encoding_header()),
641+
Decision::F6AcceptEncodingExists => DecisionResult::wrap(context.request.has_accept_encoding_header(),
642+
"accept encoding exists"),
638643
Decision::F7AcceptableEncodingAvailable => match content_negotiation::matching_encoding(resource, &context.request) {
639644
Some(encoding) => {
640645
context.selected_encoding = Some(encoding.clone());
641646
if encoding != "identity" {
642647
context.response.add_header("Content-Encoding", vec![HeaderValue::parse_string(&encoding)]);
643648
}
644-
DecisionResult::True
649+
DecisionResult::True("acceptable encoding is available".to_string())
645650
},
646-
None => DecisionResult::False
651+
None => DecisionResult::False("acceptable encoding is not available".to_string())
647652
},
648653
Decision::G7ResourceExists => {
649654
let callback = resource.resource_exists.lock().unwrap();
650-
DecisionResult::wrap(callback.deref()(context, resource))
655+
DecisionResult::wrap(callback.deref()(context, resource), "resource exists")
651656
},
652-
Decision::G8IfMatchExists => DecisionResult::wrap(context.request.has_header("If-Match")),
657+
Decision::G8IfMatchExists => DecisionResult::wrap(context.request.has_header("If-Match"),
658+
"match exists"),
653659
Decision::G9IfMatchStarExists | &Decision::H7IfMatchStarExists => DecisionResult::wrap(
654-
context.request.has_header_value("If-Match", "*")),
655-
Decision::G11EtagInIfMatch => DecisionResult::wrap(resource_etag_matches_header_values(resource, context, "If-Match")),
656-
Decision::H10IfUnmodifiedSinceExists => DecisionResult::wrap(context.request.has_header("If-Unmodified-Since")),
657-
Decision::H11IfUnmodifiedSinceValid => DecisionResult::wrap(validate_header_date(&context.request, "If-Unmodified-Since", &mut context.if_unmodified_since)),
660+
context.request.has_header_value("If-Match", "*"), "match star exists"),
661+
Decision::G11EtagInIfMatch => DecisionResult::wrap(resource_etag_matches_header_values(resource, context, "If-Match"),
662+
"etag in if match"),
663+
Decision::H10IfUnmodifiedSinceExists => DecisionResult::wrap(context.request.has_header("If-Unmodified-Since"),
664+
"unmodified since exists"),
665+
Decision::H11IfUnmodifiedSinceValid => DecisionResult::wrap(validate_header_date(&context.request, "If-Unmodified-Since", &mut context.if_unmodified_since),
666+
"unmodified since valid"),
658667
Decision::H12LastModifiedGreaterThanUMS => {
659668
match context.if_unmodified_since {
660669
Some(unmodified_since) => {
661670
let callback = resource.last_modified.lock().unwrap();
662671
match callback.deref()(context, resource) {
663-
Some(datetime) => DecisionResult::wrap(datetime > unmodified_since),
664-
None => DecisionResult::False
672+
Some(datetime) => DecisionResult::wrap(datetime > unmodified_since,
673+
"resource last modified date is greater than unmodified since"),
674+
None => DecisionResult::False("resource has no last modified date".to_string())
665675
}
666676
},
667-
None => DecisionResult::False
677+
None => DecisionResult::False("resource does not provide last modified date".to_string())
668678
}
669679
},
670680
Decision::I7Put => if context.request.is_put() {
671681
context.new_resource = true;
672-
DecisionResult::True
682+
DecisionResult::True("is a PUT request".to_string())
673683
} else {
674-
DecisionResult::False
684+
DecisionResult::False("is not a PUT request".to_string())
675685
},
676-
Decision::I12IfNoneMatchExists => DecisionResult::wrap(context.request.has_header("If-None-Match")),
677-
Decision::I13IfNoneMatchStarExists => DecisionResult::wrap(context.request.has_header_value("If-None-Match", "*")),
678-
Decision::J18GetHead => DecisionResult::wrap(context.request.is_get_or_head()),
686+
Decision::I12IfNoneMatchExists => DecisionResult::wrap(context.request.has_header("If-None-Match"),
687+
"none match exists"),
688+
Decision::I13IfNoneMatchStarExists => DecisionResult::wrap(context.request.has_header_value("If-None-Match", "*"),
689+
"none match star exists"),
690+
Decision::J18GetHead => DecisionResult::wrap(context.request.is_get_or_head(),
691+
"is GET or HEAD request"),
679692
Decision::K7ResourcePreviouslyExisted => {
680693
let callback = resource.previously_existed.lock().unwrap();
681-
DecisionResult::wrap(callback.deref()(context, resource))
694+
DecisionResult::wrap(callback.deref()(context, resource), "resource previously existed")
682695
},
683-
Decision::K13ETagInIfNoneMatch => DecisionResult::wrap(resource_etag_matches_header_values(resource, context, "If-None-Match")),
696+
Decision::K13ETagInIfNoneMatch => DecisionResult::wrap(resource_etag_matches_header_values(resource, context, "If-None-Match"),
697+
"ETag in if none match"),
684698
Decision::L5HasMovedTemporarily => {
685699
let callback = resource.moved_temporarily.lock().unwrap();
686700
match callback.deref()(context, resource) {
687701
Some(location) => {
688702
context.response.add_header("Location", vec![HeaderValue::basic(&location)]);
689-
DecisionResult::True
703+
DecisionResult::True("resource has moved temporarily".to_string())
690704
},
691-
None => DecisionResult::False
705+
None => DecisionResult::False("resource has not moved temporarily".to_string())
692706
}
693707
},
694-
Decision::L7Post | &Decision::M5Post | &Decision::N16Post => DecisionResult::wrap(context.request.is_post()),
695-
Decision::L13IfModifiedSinceExists => DecisionResult::wrap(context.request.has_header("If-Modified-Since")),
708+
Decision::L7Post | &Decision::M5Post | &Decision::N16Post => DecisionResult::wrap(context.request.is_post(),
709+
"a POST request"),
710+
Decision::L13IfModifiedSinceExists => DecisionResult::wrap(context.request.has_header("If-Modified-Since"),
711+
"if modified since exists"),
696712
Decision::L14IfModifiedSinceValid => DecisionResult::wrap(validate_header_date(&context.request,
697-
"If-Modified-Since", &mut context.if_modified_since)),
713+
"If-Modified-Since", &mut context.if_modified_since), "modified since valid"),
698714
Decision::L15IfModifiedSinceGreaterThanNow => {
699715
let datetime = context.if_modified_since.unwrap();
700716
let timezone = datetime.timezone();
701-
DecisionResult::wrap(datetime > Utc::now().with_timezone(&timezone))
717+
DecisionResult::wrap(datetime > Utc::now().with_timezone(&timezone),
718+
"modified since greater than now")
702719
},
703720
Decision::L17IfLastModifiedGreaterThanMS => {
704721
match context.if_modified_since {
705722
Some(unmodified_since) => {
706723
let callback = resource.last_modified.lock().unwrap();
707724
match callback.deref()(context, resource) {
708-
Some(datetime) => DecisionResult::wrap(datetime > unmodified_since),
709-
None => DecisionResult::False
725+
Some(datetime) => DecisionResult::wrap(datetime > unmodified_since,
726+
"last modified greater than modified since"),
727+
None => DecisionResult::False("resource has no last modified date".to_string())
710728
}
711729
},
712-
None => DecisionResult::False
730+
None => DecisionResult::False("resource does not return if_modified_since".to_string())
713731
}
714732
},
715733
Decision::I4HasMovedPermanently | &Decision::K5HasMovedPermanently => {
716734
let callback = resource.moved_permanently.lock().unwrap();
717735
match callback.deref()(context, resource) {
718736
Some(location) => {
719737
context.response.add_header("Location", vec![HeaderValue::basic(&location)]);
720-
DecisionResult::True
738+
DecisionResult::True("resource has moved permanently".to_string())
721739
},
722-
None => DecisionResult::False
740+
None => DecisionResult::False("resource has not moved permanently".to_string())
723741
}
724742
},
725743
Decision::M7PostToMissingResource | &Decision::N5PostToMissingResource => {
726744
let callback = resource.allow_missing_post.lock().unwrap();
727745
if callback.deref()(context, resource) {
728746
context.new_resource = true;
729-
DecisionResult::True
747+
DecisionResult::True("resource allows POST to missing resource".to_string())
730748
} else {
731-
DecisionResult::False
749+
DecisionResult::False("resource does not allow POST to missing resource".to_string())
732750
}
733751
},
734-
Decision::M16Delete => DecisionResult::wrap(context.request.is_delete()),
752+
Decision::M16Delete => DecisionResult::wrap(context.request.is_delete(),
753+
"a DELETE request"),
735754
Decision::M20DeleteEnacted => {
736755
let callback = resource.delete_resource.lock().unwrap();
737756
match callback.deref()(context, resource) {
738-
Ok(result) => DecisionResult::wrap(result),
757+
Ok(result) => DecisionResult::wrap(result, "resource DELETE succeeded"),
739758
Err(status) => DecisionResult::StatusCode(status)
740759
}
741760
},
@@ -749,40 +768,40 @@ fn execute_decision(
749768
let new_path = join_paths(&base_path, &sanitise_path(&path));
750769
context.request.request_path = path.clone();
751770
context.response.add_header("Location", vec![HeaderValue::basic(&new_path)]);
752-
DecisionResult::wrap(context.redirect)
771+
DecisionResult::wrap(context.redirect, "should redirect")
753772
},
754773
Err(status) => DecisionResult::StatusCode(status)
755774
}
756775
} else {
757776
let callback = resource.process_post.lock().unwrap();
758777
match callback.deref()(context, resource) {
759-
Ok(_) => DecisionResult::wrap(context.redirect),
778+
Ok(_) => DecisionResult::wrap(context.redirect, "processing POST succeeded"),
760779
Err(status) => DecisionResult::StatusCode(status)
761780
}
762781
}
763782
},
764783
Decision::P3Conflict | &Decision::O14Conflict => {
765784
let callback = resource.is_conflict.lock().unwrap();
766-
DecisionResult::wrap(callback.deref()(context, resource))
785+
DecisionResult::wrap(callback.deref()(context, resource), "resource conflict")
767786
},
768787
Decision::P11NewResource => {
769788
if context.request.is_put() {
770789
let callback = resource.process_put.lock().unwrap();
771790
match callback.deref()(context, resource) {
772-
Ok(_) => DecisionResult::wrap(context.new_resource),
791+
Ok(_) => DecisionResult::wrap(context.new_resource, "process PUT succeeded"),
773792
Err(status) => DecisionResult::StatusCode(status)
774793
}
775794
} else {
776-
DecisionResult::wrap(context.new_resource)
795+
DecisionResult::wrap(context.new_resource, "new resource creation succeeded")
777796
}
778797
},
779-
Decision::O16Put => DecisionResult::wrap(context.request.is_put()),
798+
Decision::O16Put => DecisionResult::wrap(context.request.is_put(), "a PUT request"),
780799
Decision::O18MultipleRepresentations => {
781800
let callback = resource.multiple_choices.lock().unwrap();
782-
DecisionResult::wrap(callback.deref()(context, resource))
801+
DecisionResult::wrap(callback.deref()(context, resource), "multiple choices exist")
783802
},
784-
Decision::O20ResponseHasBody => DecisionResult::wrap(context.response.has_body()),
785-
_ => DecisionResult::False
803+
Decision::O20ResponseHasBody => DecisionResult::wrap(context.response.has_body(), "response has a body"),
804+
_ => DecisionResult::False("default decision is false".to_string())
786805
}
787806
}
788807

@@ -804,13 +823,13 @@ fn execute_state_machine(context: &mut WebmachineContext, resource: &WebmachineR
804823
},
805824
&Transition::Branch(ref decision_true, ref decision_false) => {
806825
match execute_decision(&state, context, resource) {
807-
DecisionResult::True => {
808-
trace!("Transitioning from {:?} to {:?} as decision is true", state, decision_true);
826+
DecisionResult::True(reason) => {
827+
trace!("Transitioning from {:?} to {:?} as decision is true -> {}", state, decision_true, reason);
809828
decisions.push((state, true, decision_true.clone()));
810829
decision_true.clone()
811830
},
812-
DecisionResult::False => {
813-
trace!("Transitioning from {:?} to {:?} as decision is false", state, decision_false);
831+
DecisionResult::False(reason) => {
832+
trace!("Transitioning from {:?} to {:?} as decision is false -> {}", state, decision_false, reason);
814833
decisions.push((state, false, decision_false.clone()));
815834
decision_false.clone()
816835
},

0 commit comments

Comments
 (0)