Skip to content

Commit

Permalink
- More doc strings
Browse files Browse the repository at this point in the history
  • Loading branch information
nwithan8 committed Feb 1, 2023
1 parent 43bc350 commit a02f0f4
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 4 deletions.
1 change: 1 addition & 0 deletions lib/dartvcr.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// DartVCR is a library that allows you to record and replay HTTP interactions
library dartvcr;

export 'src/advanced_options.dart';
Expand Down
11 changes: 11 additions & 0 deletions lib/src/advanced_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,24 @@ class AdvancedOptions {
final bool simulateDelay;

/// The time frame during which a request can be replayed.
///
/// If the request is replayed outside of this time frame, the [whenExpired] action will be taken.
final TimeFrame validTimeFrame;

/// The action to take when a request is replayed outside of the [validTimeFrame].
final ExpirationAction whenExpired;

/// Creates a new [AdvancedOptions].
///
/// ```dart
/// final options = AdvancedOptions(
/// censors: Censors.defaultCensors,
/// matchRules: MatchRules.defaultMatchRules,
/// manualDelay: 0,
/// simulateDelay: false,
/// validTimeFrame: TimeFrame.forever,
/// whenExpired: ExpirationAction.warn,
/// );
AdvancedOptions(
{Censors? censors,
MatchRules? matchRules,
Expand Down
4 changes: 4 additions & 0 deletions lib/src/cassette.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class Cassette {
bool _locked = false;

/// Creates a new [Cassette] with the given [name] and [folderPath].
///
/// ```dart
/// Cassette cassette = Cassette('my_cassette', 'cassettes');
/// ```
Cassette(folderPath, this.name) : _filePath = '$folderPath/$name.json';

/// Returns the number of interactions in the cassette.
Expand Down
14 changes: 14 additions & 0 deletions lib/src/censor_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,23 @@ class CensorElement {
final bool caseSensitive;

/// Creates a new [CensorElement] with the given [name] and [caseSensitive] flag.
///
/// ```dart
/// CensorElement element = CensorElement('key', caseSensitive: true);
/// ```
CensorElement(this.name, {this.caseSensitive = false});

/// Returns true if the given [key] matches this element.
///
/// ```dart
/// CensorElement element1 = CensorElement('key');
/// element1.matches('key'); // true
/// element1.matches('KEY'); // true
///
/// CensorElement element2 = CensorElement('key', caseSensitive: true);
/// element2.matches('key'); // true
/// element2.matches('KEY'); // false
/// ```
bool matches(String key) {
if (caseSensitive) {
return key == name;
Expand Down
9 changes: 9 additions & 0 deletions lib/src/censors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class Censors {
final List<CensorElement> _queryElementsToCensor;

/// Creates a new [Censors] with the given [censorString].
///
/// ```dart
/// Censors censors = Censors(censorString: "censored");
/// ```
Censors({String censorString = "******"})
: _censorString = censorString,
_bodyElementsToCensor = [],
Expand All @@ -34,6 +38,7 @@ class Censors {
}

/// Add the given [keys] to the list of elements to censor from request bodies.
///
/// If [caseSensitive] is true, the keys will be censored only when matching case exactly.
Censors censorBodyElementsByKeys(List<String> keys,
{bool caseSensitive = false}) {
Expand All @@ -49,6 +54,7 @@ class Censors {
}

/// Add the given [keys] to the list of elements to censor from request headers.
///
/// If [caseSensitive] is true, the keys will be censored only when matching case exactly.
Censors censorHeaderElementsByKeys(List<String> keys,
{bool caseSensitive = false}) {
Expand All @@ -64,6 +70,7 @@ class Censors {
}

/// Add the given [keys] to the list of elements to censor from request query parameters.
///
/// If [caseSensitive] is true, the keys will be censored only when matching case exactly.
Censors censorQueryElementsByKeys(List<String> keys,
{bool caseSensitive = false}) {
Expand All @@ -73,6 +80,8 @@ class Censors {
}

/// Applies the body parameter censors to the given [body] with the given [contentType].
///
/// Currently only supports JSON bodies.
// TODO: Only works on JSON bodies
String applyBodyParameterCensors(String body, ContentType contentType) {
if (body.isEmpty) {
Expand Down
15 changes: 15 additions & 0 deletions lib/src/dartvcr_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,26 @@ class DartVCRClient extends http.BaseClient {
final AdvancedOptions _advancedOptions;

/// Creates a new [DartVCRClient] with the given [Cassette], [Mode] and [AdvancedOptions].
///
/// ```dart
/// final client = DartVCRClient(
/// cassette: Cassette(),
/// mode: Mode.auto,
/// advancedOptions: AdvancedOptions(
/// censors: Censors.defaultCensors,
/// matchRules: MatchRules.defaultMatchRules,
/// manualDelay: 0,
/// simulateDelay: false,
/// validTimeFrame: TimeFrame.forever,
/// whenExpired: ExpirationAction.warn,
/// ),
/// );
DartVCRClient(this._cassette, this._mode, {AdvancedOptions? advancedOptions})
: _client = http.Client(),
_advancedOptions = advancedOptions ?? AdvancedOptions();

/// Simulates an HTTP request and response.
///
/// Makes a real request and records the response if the [Mode] is [Mode.record].
/// Drops the request and returns a recorded response if the [Mode] is [Mode.replay].
/// Makes a real request and returns the real response if the [Mode] is [Mode.bypass].
Expand Down
1 change: 1 addition & 0 deletions lib/src/expiration_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum ExpirationAction {
recordAgain,
}

/// An extension on [ExpirationAction] that provides additional functionality.
extension ExpirationActionExtension on ExpirationAction {
void checkCompatibleSettings(ExpirationAction action, Mode mode) {
if (action == ExpirationAction.recordAgain && mode == Mode.replay) {
Expand Down
53 changes: 53 additions & 0 deletions lib/src/internal_utilities/content_type.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
/// The various content types available.
enum ContentType {
/// JSON content type.
json,

/// XML content type.
xml,

/// Text content type.
text,

/// HTML content type.
html,
}

/// Determines the content type from the given [content].
///
/// Returns the [ContentType] if the content type can be determined.
/// Returns null if the content type cannot be determined.
///
/// ```dart
/// determineContentType("{\"key\": \"value\"}"); // ContentType.json
/// determineContentType("<xml></xml>"); // ContentType.xml
/// determineContentType("<!DOCTYPE html><html></html>"); // ContentType.html
/// determineContentType("text"); // ContentType.text
/// determineContentType(""); // null
/// ```
ContentType? determineContentType(String content) {
if (content.isEmpty) {
return null;
Expand All @@ -21,6 +41,15 @@ ContentType? determineContentType(String content) {
}
}

/// Converts the given [contentType] string to a [ContentType].
///
/// ```dart
/// fromString("{\"key\": \"value\"}"); // ContentType.json
/// fromString("<xml></xml>"); // ContentType.xml
/// fromString("<!DOCTYPE html><html></html>"); // ContentType.html
/// fromString("text"); // ContentType.text
/// fromString(null); // null
/// ```
ContentType? fromString(String? contentType) {
if (contentType == null) {
return null;
Expand All @@ -40,14 +69,38 @@ ContentType? fromString(String? contentType) {
}
}

/// Checks if the given [content] is JSON.
///
/// Returns true if the content is JSON, false otherwise.
///
/// ```dart
/// isJson("{\"key\": \"value\"}"); // true
/// isJson("text"); // false
/// ```
bool isJson(String content) {
return content.startsWith("{") || content.startsWith("[");
}

/// Checks if the given [content] is XML.
///
/// Returns true if the content is XML, false otherwise.
///
/// ```dart
/// isXml("<xml></xml>"); // true
/// isXml("text"); // false
/// ```
bool isXml(String content) {
return content.startsWith("<") && content.endsWith(">");
}

/// Checks if the given [content] is HTML.
///
/// Returns true if the content is HTML, false otherwise.
///
/// ```dart
/// isHtml("<!DOCTYPE html><html></html>"); // true
/// isHtml("text"); // false
/// ```
bool isHtml(String content) {
return content.startsWith("<!DOCTYPE html>") && content.endsWith("</html>");
}
36 changes: 35 additions & 1 deletion lib/src/match_rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class MatchRules {
final List<MatchRule> _rules;

/// Creates a new [MatchRules].
///
/// ```dart
/// MatchRules rules = MatchRules();
/// ```
MatchRules() : _rules = [];

/// A pre-configured set of rules that will match requests based on the full URL and method.
Expand Down Expand Up @@ -38,6 +42,10 @@ class MatchRules {
}

/// Enforces that both requests have the base URL (host).
///
/// ```dart
/// MatchRules rules = MatchRules().byBaseUrl();
/// ```
MatchRules byBaseUrl() {
_by((Request received, Request recorded) {
return received.uri.host == recorded.uri.host;
Expand All @@ -46,8 +54,13 @@ class MatchRules {
}

/// Enforces that both requests have the same full URL.
///
/// If [preserveQueryOrder] is true, the order of the query parameters must match as well.
/// If [preserveQueryOrder] is false, the order of the query parameters is ignored.
///
/// ```dart
/// MatchRules rules = MatchRules().byFullUrl();
/// ```
MatchRules byFullUrl({bool preserveQueryOrder = false}) {
_by((Request received, Request recorded) {
if (preserveQueryOrder) {
Expand Down Expand Up @@ -76,6 +89,9 @@ class MatchRules {
}

/// Enforces that both requests have the same HTTP method.
///
/// ```dart
/// MatchRules rules = MatchRules().byMethod();
MatchRules byMethod() {
_by((Request received, Request recorded) {
return received.method == recorded.method;
Expand All @@ -84,7 +100,11 @@ class MatchRules {
}

/// Enforces that both requests have the same body.
///
/// Ignore specific [ignoreElements] in the body when comparing.
///
/// ```dart
/// MatchRules rules = MatchRules().byBody();
MatchRules byBody({List<CensorElement> ignoreElements = const []}) {
_by((Request received, Request recorded) {
if (received.body == null && recorded.body == null) {
Expand All @@ -109,6 +129,10 @@ class MatchRules {
}

/// Enforces that both requests are the exact same (headers, body, etc.).
///
/// ```dart
/// MatchRules rules = MatchRules().byEverything();
/// ```
MatchRules byEverything() {
_by((Request received, Request recorded) {
String receivedRequest = jsonEncode(received);
Expand All @@ -119,6 +143,10 @@ class MatchRules {
}

/// Enforces that both requests have the same header with the given [headerKey].
///
/// ```dart
/// MatchRules rules = MatchRules().byHeader("Content-Type");
/// ```
MatchRules byHeader(String headerKey) {
_by((Request received, Request recorded) {
if (received.headers.containsKey(headerKey) &&
Expand All @@ -132,9 +160,14 @@ class MatchRules {
}

/// Enforces that both requests have the same headers.
///
/// If [exact] is true, then both requests must have the exact same headers.
/// If [exact] is false, then as long as the evaluated request has all the headers of the matching request (and potentially more), the match is considered valid.
MatchRules byHeaders(bool exact) {
///
/// ```dart
/// MatchRules rules = MatchRules().byHeaders();
/// ```
MatchRules byHeaders({bool exact = false}) {
if (exact) {
// first, we'll check that there are the same number of headers in both requests. If they're are, then the second check is guaranteed to compare all headers.
_by((Request received, Request recorded) {
Expand All @@ -160,4 +193,5 @@ class MatchRules {
}
}

/// A function that determines if two requests match.
typedef MatchRule = bool Function(Request received, Request recorded);
4 changes: 4 additions & 0 deletions lib/src/request_elements/http_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import 'package:json_annotation/json_annotation.dart';

part 'http_element.g.dart';

/// The base class for all request elements.
@JsonSerializable(explicitToJson: true)
class HttpElement {
/// Creates a new [HttpElement].
HttpElement();

/// Creates a new [HttpElement] from a JSON map.
factory HttpElement.fromJson(Map<String, dynamic> input) =>
_$HttpElementFromJson(input);

/// Converts this [HttpElement] to a JSON map.
Map<String, dynamic> toJson() => _$HttpElementToJson(this);
}
Loading

0 comments on commit a02f0f4

Please sign in to comment.