diff --git a/.appveyor.yml b/.appveyor.yml index c75361a..1704e61 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -57,7 +57,8 @@ init: install: - ps: | Execute-Action "installing tools" { - choco install codecov msbuild-sonarqube-runner opencover.portable + choco install codecov opencover.portable -y + dotnet tool install --global dotnet-sonarscanner } dotnet_csproj: @@ -74,19 +75,10 @@ before_build: - ps: | Execute-Action "beginning code analysis" { if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { - # For security reasons, AppVeyor only exposes secure variables for pull requests from the same repositoy. - # If $env:SONARQUBE_GITHUB_TOKEN is not set, then it is a pull request from another repository and we'll - # skip SonarQube analysis. - if ($env:SONARQUBE_GITHUB_TOKEN) { - MSBuild.SonarQube.Runner.exe begin /o:$env:SONARQUBE_ORGANIZATION /k:$env:APPVEYOR_PROJECT_NAME /v:$env:APPVEYOR_BUILD_VERSION /d:sonar.host.url=https://sonarcloud.io /d:sonar.login=$env:SONARQUBE_TOKEN /d:sonar.cs.opencover.reportsPaths=coverage.xml /d:sonar.coverage.exclusions=**/*Tests.cs,**/*Tests.*.cs,**/*Mock.cs /d:sonar.github.pullRequest=$env:APPVEYOR_PULL_REQUEST_NUMBER /d:sonar.github.repository=$env:APPVEYOR_REPO_NAME /d:sonar.github.oauth=$env:SONARQUBE_GITHUB_TOKEN - - $env:SONARQUBE_RUNNING = $true - } + dotnet sonarscanner begin /o:$env:SONARQUBE_ORGANIZATION /k:$env:APPVEYOR_PROJECT_NAME /v:$env:APPVEYOR_BUILD_VERSION /d:sonar.host.url=https://sonarcloud.io /d:sonar.login=$env:SONARQUBE_TOKEN /d:sonar.cs.opencover.reportsPaths=coverage.xml /d:sonar.coverage.exclusions=**/*Tests.cs /d:sonar.github.pullRequest=$env:APPVEYOR_PULL_REQUEST_NUMBER /d:sonar.github.repository=$env:APPVEYOR_REPO_NAME /d:sonar.github.oauth=$env:SONARQUBE_GITHUB_TOKEN } else { - MSBuild.SonarQube.Runner.exe begin /o:$env:SONARQUBE_ORGANIZATION /k:$env:APPVEYOR_PROJECT_NAME /v:$env:APPVEYOR_BUILD_VERSION /d:sonar.host.url=https://sonarcloud.io /d:sonar.login=$env:SONARQUBE_TOKEN /d:sonar.cs.opencover.reportsPaths=coverage.xml /d:sonar.coverage.exclusions=**/*Tests.cs,**/*Tests.*.cs,**/*Mock.cs - - $env:SONARQUBE_RUNNING = $true + dotnet sonarscanner begin /o:$env:SONARQUBE_ORGANIZATION /k:$env:APPVEYOR_PROJECT_NAME /v:$env:APPVEYOR_BUILD_VERSION /d:sonar.host.url=https://sonarcloud.io /d:sonar.login=$env:SONARQUBE_TOKEN /d:sonar.cs.opencover.reportsPaths=coverage.xml /d:sonar.coverage.exclusions=**/*Tests.cs } } @@ -134,9 +126,7 @@ after_test: - ps: | Execute-Action "ending code analysis" { - if ($env:SONARQUBE_RUNNING -eq $true) { - MSBuild.SonarQube.Runner.exe end /d:sonar.login=$env:SONARQUBE_TOKEN - } + dotnet sonarscanner end /d:sonar.login=$env:SONARQUBE_TOKEN } artifacts: diff --git a/src/PartialResponse.Core/DelimiterOptions.cs b/src/PartialResponse.Core/DelimiterOptions.cs new file mode 100644 index 0000000..b4a8db6 --- /dev/null +++ b/src/PartialResponse.Core/DelimiterOptions.cs @@ -0,0 +1,95 @@ +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace PartialResponse.Core +{ + /// + /// Represents delimiter options for parser + /// + public class DelimiterOptions + { + private static DelimiterOptions defaultOptions; + + /// + /// Initializes a new instance of the class. + /// + /// Characters representing field delimiters. + /// Characters representing nested field delimiters. + /// Characters representing field group start delimiters. + /// Characters representing field group end delimiters. + public DelimiterOptions(char[] fieldsDelimiters, char[] nestedFieldDelimiters, char[] fieldGroupStartDelimiters, char[] fieldGroupEndDelimiters) + { + var map = new Dictionary(); + + foreach (var c in fieldsDelimiters ?? throw new ArgumentNullException(nameof(fieldsDelimiters))) + { + map.Add(c, TokenType.FieldsDelimiter); + } + + foreach (var c in nestedFieldDelimiters ?? throw new ArgumentNullException(nameof(nestedFieldDelimiters))) + { + map.Add(c, TokenType.NestedFieldDelimiter); + } + + foreach (var c in fieldGroupStartDelimiters ?? throw new ArgumentNullException(nameof(fieldGroupStartDelimiters))) + { + map.Add(c, TokenType.FieldGroupStartDelimiter); + } + + foreach (var c in fieldGroupEndDelimiters ?? throw new ArgumentNullException(nameof(fieldGroupEndDelimiters))) + { + map.Add(c, TokenType.FieldGroupEndDelimiter); + } + + this.DelimiterToTokenTypeMap = map; + + this.FieldsDelimiters = fieldsDelimiters; + this.NestedFieldDelimiters = nestedFieldDelimiters; + this.FieldGroupStartDelimiters = fieldGroupStartDelimiters; + this.FieldGroupEndDelimiters = fieldGroupEndDelimiters; + } + + /// + /// Gets default options for parser. Default options are: + /// field delimiters - ',' + /// nested field delimiters - '/' + /// field group start delimiters - '(' + /// field group end delimiters - ')' + /// + public static DelimiterOptions DefaultOptions + { + get + { + return defaultOptions + ?? (defaultOptions = new DelimiterOptions(new[] { ',' }, new[] { '/' }, new[] { '(' }, new[] { ')' })); + } + } + + /// + /// Gets delimiter to token type map. + /// + public IReadOnlyDictionary DelimiterToTokenTypeMap { get; } + + /// + /// Gets fields delimiters. + /// + public char[] FieldsDelimiters { get; } + + /// + /// Gets nested field delimiters. + /// + public char[] NestedFieldDelimiters { get; } + + /// + /// Gets nested field group start delimiters. + /// + public char[] FieldGroupStartDelimiters { get; } + + /// + /// Gets nested field group end delimiters. + /// + public char[] FieldGroupEndDelimiters { get; } + } +} \ No newline at end of file diff --git a/src/PartialResponse.Core/Field.cs b/src/PartialResponse.Core/Field.cs index ba9c10a..ef73b27 100644 --- a/src/PartialResponse.Core/Field.cs +++ b/src/PartialResponse.Core/Field.cs @@ -1,6 +1,7 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; +using System.Linq; namespace PartialResponse.Core { @@ -16,15 +17,20 @@ public struct Field /// /// Initializes a new instance of the structure. /// - /// The value of the field. - public Field(string value) + /// The value of the field. + public Field(params string[] parts) { - if (value == null) + if (parts == null) + { + throw new ArgumentNullException(nameof(parts)); + } + + if (parts.Length == 0 || parts.Any(string.IsNullOrEmpty)) { - throw new ArgumentNullException(nameof(value)); + throw new ArgumentException("Parts cannot be empty or null", nameof(parts)); } - this.Parts = value.Split('/'); + this.Parts = parts; } /// @@ -77,14 +83,5 @@ public bool Matches(string[] parts, bool ignoreCase) return true; } - - /// - /// Returns a string that represents the current object. - /// - /// A string that represents the current object. - public override string ToString() - { - return string.Join("/", this.Parts); - } } } \ No newline at end of file diff --git a/src/PartialResponse.Core/Fields.cs b/src/PartialResponse.Core/Fields.cs index 0d40e93..b9cc982 100644 --- a/src/PartialResponse.Core/Fields.cs +++ b/src/PartialResponse.Core/Fields.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using System.Collections.Generic; @@ -14,7 +14,7 @@ namespace PartialResponse.Core /// from your code. public struct Fields { - private IEnumerable values; + private readonly IEnumerable values; private Fields(IEnumerable values) { @@ -36,8 +36,10 @@ public IEnumerable Values /// The value. /// When this method returns, contains the equivalent of the value, /// if the conversion succeeded, or null if the conversion failed. + /// Optional options which allow to specify custom delimiters. If no value provided + /// a default options are used. /// true if value was converted successfully; otherwise, false. - public static bool TryParse(string value, out Fields result) + public static bool TryParse(string value, out Fields result, DelimiterOptions options = null) { if (value == null) { @@ -46,7 +48,7 @@ public static bool TryParse(string value, out Fields result) using (var reader = new StringReader(value)) { - var context = new ParserContext(reader); + var context = new ParserContext(reader, options ?? DelimiterOptions.DefaultOptions); var parser = new Parser(context); parser.Parse(); @@ -68,10 +70,12 @@ public static bool TryParse(string value, out Fields result) /// Indicates whether a field matches the specified value. /// /// The value to match. + /// Delimiter options to use when matching. If no options provided then the + /// default options are used. /// true if a field matches the specified value; otherwise, false. - public bool Matches(string value) + public bool Matches(string value, DelimiterOptions delimiterOptions = null) { - return this.Matches(value, false); + return this.Matches(value, false, delimiterOptions); } /// @@ -79,15 +83,23 @@ public bool Matches(string value) /// /// The value to match. /// A value which indicates whether matching should be case-insensitive. + /// Delimiter options to use when matching. If no options provided then the + /// default options are used. /// true if a field matches the specified value; otherwise, false. - public bool Matches(string value, bool ignoreCase) + public bool Matches(string value, bool ignoreCase, DelimiterOptions delimiterOptions = null) { if (value == null) { throw new ArgumentNullException(nameof(value)); } - var parts = value.Split('/'); + if (!this.Values.Any()) + { + return false; + } + + var separators = (delimiterOptions ?? DelimiterOptions.DefaultOptions).NestedFieldDelimiters; + var parts = value.Split(separators); return this.Values.Any(field => field.Matches(parts, ignoreCase)); } diff --git a/src/PartialResponse.Core/Parser.cs b/src/PartialResponse.Core/Parser.cs index 01d07b5..2e90880 100644 --- a/src/PartialResponse.Core/Parser.cs +++ b/src/PartialResponse.Core/Parser.cs @@ -1,7 +1,8 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using System.Collections.Generic; +using System.Linq; namespace PartialResponse.Core { @@ -12,7 +13,7 @@ namespace PartialResponse.Core /// from your code. public class Parser { - private readonly Stack prefixes = new Stack(); + private readonly Stack> prefixes = new Stack>(); private readonly Dictionary handlers; private readonly ParserContext context; private readonly Tokenizer tokenizer; @@ -34,13 +35,14 @@ public Parser(ParserContext context) this.context = context; - this.tokenizer = new Tokenizer(context.Source); + this.tokenizer = new Tokenizer(context.Source, context.Options.DelimiterToTokenTypeMap); + this.handlers = new Dictionary { - { TokenType.ForwardSlash, this.HandleForwardSlash }, - { TokenType.LeftParenthesis, this.HandleLeftParenthesis }, - { TokenType.RightParenthesis, this.HandleRightParenthesis }, - { TokenType.Comma, this.HandleComma }, + { TokenType.NestedFieldDelimiter, this.HandleNestedFieldDelimiter }, + { TokenType.FieldGroupStartDelimiter, this.HandleFieldGroupStartDelimiter }, + { TokenType.FieldGroupEndDelimiter, this.HandleFieldGroupEndDelimiter }, + { TokenType.FieldsDelimiter, this.HandleFieldsDelimiter }, { TokenType.Eof, this.HandleEof } }; } @@ -78,26 +80,26 @@ private void HandleIdentifier(bool acceptEnd) return; } - string prefix; + List prefixes; if (this.prefixes.Count > 0) { - var previousPrefix = this.depth > 0 && this.previousToken.Type != TokenType.ForwardSlash ? this.prefixes.Peek() : this.prefixes.Pop(); + prefixes = this.depth > 0 && this.previousToken.Type != TokenType.NestedFieldDelimiter + ? this.prefixes.Peek().ToList() // creating a copy of existing prefixes when we are diving deep + : this.prefixes.Pop(); - prefix = $"{previousPrefix}/{this.currentToken.Value}"; + prefixes.Add(this.currentToken.Value); } else { - prefix = this.currentToken.Value; + prefixes = new List { this.currentToken.Value }; } - this.prefixes.Push(prefix); + this.prefixes.Push(prefixes); this.NextToken(); - Action handler; - - if (!this.handlers.TryGetValue(this.currentToken.Type, out handler)) + if (!this.handlers.TryGetValue(this.currentToken.Type, out var handler)) { this.context.Error = new UnexpectedTokenError(this.currentToken); @@ -107,13 +109,13 @@ private void HandleIdentifier(bool acceptEnd) handler(); } - private void HandleForwardSlash() + private void HandleNestedFieldDelimiter() { this.NextToken(); this.HandleIdentifier(acceptEnd: false); } - private void HandleLeftParenthesis() + private void HandleFieldGroupStartDelimiter() { this.depth++; @@ -121,7 +123,7 @@ private void HandleLeftParenthesis() this.HandleIdentifier(acceptEnd: false); } - private void HandleRightParenthesis() + private void HandleFieldGroupEndDelimiter() { do { @@ -129,7 +131,7 @@ private void HandleRightParenthesis() if (this.previousToken.Type == TokenType.Identifier) { - this.context.Values.Add(new Field(value)); + this.context.Values.Add(new Field(value.ToArray())); } this.depth--; @@ -143,11 +145,11 @@ private void HandleRightParenthesis() this.NextToken(); } - while (this.currentToken.Type == TokenType.RightParenthesis); + while (this.currentToken.Type == TokenType.FieldGroupEndDelimiter); if (this.currentToken.Type != TokenType.Eof) { - if (this.currentToken.Type != TokenType.Comma) + if (this.currentToken.Type != TokenType.FieldsDelimiter) { this.context.Error = new UnexpectedTokenError(this.currentToken); @@ -161,11 +163,11 @@ private void HandleRightParenthesis() } } - private void HandleComma() + private void HandleFieldsDelimiter() { var value = this.prefixes.Pop(); - this.context.Values.Add(new Field(value)); + this.context.Values.Add(new Field(value.ToArray())); this.NextToken(); this.HandleIdentifier(acceptEnd: false); @@ -182,7 +184,7 @@ private void HandleEof() var value = this.prefixes.Pop(); - this.context.Values.Add(new Field(value)); + this.context.Values.Add(new Field(value.ToArray())); } private void NextToken() diff --git a/src/PartialResponse.Core/ParserContext.cs b/src/PartialResponse.Core/ParserContext.cs index 2573886..9a04693 100644 --- a/src/PartialResponse.Core/ParserContext.cs +++ b/src/PartialResponse.Core/ParserContext.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using System.Collections.Generic; @@ -17,14 +17,21 @@ public class ParserContext /// Initializes a new instance of the class. /// /// A representing the input string. - public ParserContext(TextReader source) + /// Delimiters options for parser. + public ParserContext(TextReader source, DelimiterOptions options) { if (source == null) { - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(source)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); } this.Source = source; + this.Options = options; this.Values = new List(); } @@ -38,12 +45,17 @@ public ParserContext(TextReader source) /// Gets the representing the input string. /// /// The representing the input string. - public TextReader Source { get; private set; } + public TextReader Source { get; } /// /// Gets the values that are extracted while parsing. /// /// The values that are extracted while parsing. - public List Values { get; private set; } + public List Values { get; } + + /// + /// Gets delimiters options. + /// + public DelimiterOptions Options { get; } } } \ No newline at end of file diff --git a/src/PartialResponse.Core/Token.cs b/src/PartialResponse.Core/Token.cs index fa19430..7d31b66 100644 --- a/src/PartialResponse.Core/Token.cs +++ b/src/PartialResponse.Core/Token.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. namespace PartialResponse.Core { diff --git a/src/PartialResponse.Core/TokenType.cs b/src/PartialResponse.Core/TokenType.cs index e960e3a..a7bb22a 100644 --- a/src/PartialResponse.Core/TokenType.cs +++ b/src/PartialResponse.Core/TokenType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. namespace PartialResponse.Core { @@ -17,24 +17,24 @@ public enum TokenType Identifier, /// - /// A forward slash ('/') delimiter. + /// A nested field delimiter, for example - forward slash ('/'). /// - ForwardSlash, + NestedFieldDelimiter, /// - /// An opening parenthesis ('('). + /// A field group start delimiter, for example - opening parenthesis ('('). /// - LeftParenthesis, + FieldGroupStartDelimiter, /// - /// A closing parenthesis (')'). + /// A field group end delimiter, for example - closing parenthesis (')'). /// - RightParenthesis, + FieldGroupEndDelimiter, /// - /// A comma (',') delimiter. + /// A fields delimiter, for example - comma (','). /// - Comma, + FieldsDelimiter, /// /// A space, horizontal tab, new line, or carriage return. Typically, a contiguous run of whitespace is a @@ -47,4 +47,4 @@ public enum TokenType /// Eof } -} \ No newline at end of file +} diff --git a/src/PartialResponse.Core/Tokenizer.cs b/src/PartialResponse.Core/Tokenizer.cs index 6011a87..49b871e 100644 --- a/src/PartialResponse.Core/Tokenizer.cs +++ b/src/PartialResponse.Core/Tokenizer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using System.Collections.Generic; @@ -15,14 +15,8 @@ namespace PartialResponse.Core public class Tokenizer { private readonly TextReader source; + private readonly IReadOnlyDictionary tokensMap; private readonly StringBuilder buffer = new StringBuilder(); - private readonly Dictionary tokens = new Dictionary() - { - ['/'] = TokenType.ForwardSlash, - ['('] = TokenType.LeftParenthesis, - [')'] = TokenType.RightParenthesis, - [','] = TokenType.Comma - }; private int position = -1; @@ -30,14 +24,21 @@ public class Tokenizer /// Initializes a new instance of the class. /// /// A representing the input string. - public Tokenizer(TextReader source) + /// A map of non-identifier tokens to their character representations. + public Tokenizer(TextReader source, IReadOnlyDictionary tokensMap) { if (source == null) { throw new ArgumentNullException(nameof(source)); } + if (tokensMap == null) + { + throw new ArgumentNullException(nameof(tokensMap)); + } + this.source = source; + this.tokensMap = tokensMap; } /// @@ -51,28 +52,29 @@ public Token NextToken() return new Token(null, TokenType.Eof, this.position); } - Token token; - - TokenType tokenType; - - if (this.tokens.TryGetValue(this.GetCurrentCharacter(), out tokenType)) + if (this.tokensMap.TryGetValue(this.GetCurrentCharacter(), out var tokenType)) { this.TakeCharacter(); - token = new Token(this.buffer.ToString(), tokenType, this.position); + return this.CreateToken(tokenType); } - else if (this.IsWhiteSpace(this.GetCurrentCharacter())) + else if (char.IsWhiteSpace(this.GetCurrentCharacter())) { - this.TakeCharactersWhile(character => this.IsWhiteSpace(character)); + this.TakeCharactersWhile(character => char.IsWhiteSpace(character)); - token = new Token(this.buffer.ToString(), TokenType.WhiteSpace, this.position); + return this.CreateToken(TokenType.WhiteSpace); } else { - this.TakeCharactersWhile(character => !this.tokens.ContainsKey(character) && !this.IsWhiteSpace(character)); + this.TakeCharactersWhile(character => !this.tokensMap.ContainsKey(character) && !char.IsWhiteSpace(character)); - token = new Token(this.buffer.ToString(), TokenType.Identifier, this.position); + return this.CreateToken(TokenType.Identifier); } + } + + private Token CreateToken(TokenType tokenType) + { + var token = new Token(this.buffer.ToString(), tokenType, this.position); this.buffer.Clear(); @@ -96,15 +98,6 @@ private void TakeCharacter() this.position++; } - private bool IsWhiteSpace(char character) - { - return - character == ' ' || - character == '\t' || - character == '\r' || - character == '\n'; - } - private char GetCurrentCharacter() { var value = this.source.Peek(); diff --git a/src/PartialResponse.Core/UnexpectedTokenError.cs b/src/PartialResponse.Core/UnexpectedTokenError.cs index 35351e8..7e40440 100644 --- a/src/PartialResponse.Core/UnexpectedTokenError.cs +++ b/src/PartialResponse.Core/UnexpectedTokenError.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. namespace PartialResponse.Core { diff --git a/stylecop.json b/stylecop.json index 1049ef0..b4c5402 100644 --- a/stylecop.json +++ b/stylecop.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": { "documentationRules": { - "copyrightText": "Copyright (c) Arjen Post. See LICENSE in the project root for license information.", + "copyrightText": "Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information.", "documentInternalElements": false, "xmlHeader": false }, diff --git a/test/PartialResponse.Core.Tests/FieldTests.cs b/test/PartialResponse.Core.Tests/FieldTests.cs index bd86303..a6b38b7 100644 --- a/test/PartialResponse.Core.Tests/FieldTests.cs +++ b/test/PartialResponse.Core.Tests/FieldTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using Xunit; @@ -8,39 +8,34 @@ namespace PartialResponse.Core.Tests public class FieldTests { [Fact] - public void TheConstructorShouldThrowIfValueIsNull() + public void TheConstructorShouldThrowIfValuesIsNull() { // Arrange - string value = null; + string[] value = null; // Act Assert.Throws(() => new Field(value)); } [Fact] - public void ThePartsPropertyShouldContainSingleValue() + public void TheConstructorShouldThrowIfOneOfValuesIsNull() { // Arrange - var value = "foo"; + string value = null; // Act - var field = new Field(value); - - // Assert - Assert.Equal(new[] { "foo" }, field.Parts); + Assert.Throws(() => new Field("bla", value)); } [Fact] - public void ThePartsPropertyShouldContainMultipleValues() + public void ThePartsPropertyShouldContainSingleValue() { // Arrange - var value = "foo/bar"; - // Act - var field = new Field(value); + var field = new Field("foo"); // Assert - Assert.Equal(new[] { "foo", "bar" }, field.Parts); + Assert.Equal(new[] { "foo" }, field.Parts); } [Fact] @@ -72,7 +67,7 @@ public void TheMatchesMethodShouldReturnFalseForDifferentValues() public void TheMatchesMethodShouldReturnFalseForDifferentNestedValues() { // Arrange - var field = new Field("foo/bar"); + var field = new Field("foo", "bar"); // Act var result = field.Matches(new[] { "foo", "baz" }); @@ -111,7 +106,7 @@ public void TheMatchesMethodShouldReturnTrueForSamePrefixValues() public void TheMatchesMethodShouldReturnTrueForOtherSuffixValue() { // Arrange - var field = new Field("foo/bar"); + var field = new Field("foo", "bar"); // Act var result = field.Matches(new[] { "foo" }); @@ -145,19 +140,5 @@ public void TheMatchesMethodShouldIgnoreCase() // Assert Assert.True(result); } - - [Fact] - public void TheToStringMethodShouldReturnValue() - { - // Arrange - var value = "foo/bar"; - var field = new Field(value); - - // Act - var result = field.Matches(new[] { "foo" }); - - // Assert - Assert.Equal(value, field.ToString()); - } } } \ No newline at end of file diff --git a/test/PartialResponse.Core.Tests/FieldsTests.cs b/test/PartialResponse.Core.Tests/FieldsTests.cs index d4e80ad..4d4f3ab 100644 --- a/test/PartialResponse.Core.Tests/FieldsTests.cs +++ b/test/PartialResponse.Core.Tests/FieldsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using Xunit; diff --git a/test/PartialResponse.Core.Tests/ParserContextTests.cs b/test/PartialResponse.Core.Tests/ParserContextTests.cs index 8f3590b..d82062e 100644 --- a/test/PartialResponse.Core.Tests/ParserContextTests.cs +++ b/test/PartialResponse.Core.Tests/ParserContextTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using System.IO; @@ -15,7 +15,17 @@ public void TheConstructorShouldThrowIfSourceIsNull() TextReader source = null; // Act - Assert.Throws(() => new ParserContext(source)); + Assert.Throws(() => new ParserContext(source, DelimiterOptions.DefaultOptions)); + } + + [Fact] + public void TheConstructorShouldThrowIfDelimiterOptionsIsNull() + { + // Arrange + DelimiterOptions options = null; + + // Act + Assert.Throws(() => new ParserContext(new StringReader(string.Empty), options)); } } } diff --git a/test/PartialResponse.Core.Tests/ParserTests.cs b/test/PartialResponse.Core.Tests/ParserTests.cs index 26d896b..e78d971 100644 --- a/test/PartialResponse.Core.Tests/ParserTests.cs +++ b/test/PartialResponse.Core.Tests/ParserTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using System.IO; @@ -24,7 +24,7 @@ public void TheParseMethodShouldParseEmptySource() { // Arrange var source = new StringReader(string.Empty); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -35,15 +35,18 @@ public void TheParseMethodShouldParseEmptySource() } [Theory] - [InlineData("/", TokenType.ForwardSlash)] - [InlineData("(", TokenType.LeftParenthesis)] - [InlineData(")", TokenType.RightParenthesis)] - [InlineData(",", TokenType.Comma)] + [InlineData("/", TokenType.NestedFieldDelimiter)] + [InlineData(".", TokenType.NestedFieldDelimiter)] + [InlineData("(", TokenType.FieldGroupStartDelimiter)] + [InlineData("[", TokenType.FieldGroupStartDelimiter)] + [InlineData(")", TokenType.FieldGroupEndDelimiter)] + [InlineData("]", TokenType.FieldGroupEndDelimiter)] + [InlineData(",", TokenType.FieldsDelimiter)] public void TheParseMethodShouldSetErrorIfIllegalTokenAtStart(string value, TokenType tokenType) { // Arrange var source = new StringReader(value); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -58,7 +61,7 @@ public void TheParseMethodShouldParseSingleIdentifier() { // Arrange var source = new StringReader("foo"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -73,7 +76,7 @@ public void TheParseMethodShouldParseNestedIdentifier() { // Arrange var source = new StringReader("foo/bar"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -84,16 +87,28 @@ public void TheParseMethodShouldParseNestedIdentifier() } [Theory] - [InlineData("/", TokenType.ForwardSlash)] - [InlineData("(", TokenType.LeftParenthesis)] - [InlineData(")", TokenType.RightParenthesis)] - [InlineData(",", TokenType.Comma)] - [InlineData("", TokenType.Eof)] - public void TheParseMethodShouldSetErrorIfIllegalTokenAfterForwardSlash(string value, TokenType tokenType) + [InlineData(".", "/", TokenType.NestedFieldDelimiter)] + [InlineData(".", ".", TokenType.NestedFieldDelimiter)] + [InlineData(".", "(", TokenType.FieldGroupStartDelimiter)] + [InlineData(".", "[", TokenType.FieldGroupStartDelimiter)] + [InlineData(".", ")", TokenType.FieldGroupEndDelimiter)] + [InlineData(".", "]", TokenType.FieldGroupEndDelimiter)] + [InlineData(".", ",", TokenType.FieldsDelimiter)] + [InlineData(".", "", TokenType.Eof)] + + [InlineData("/", "/", TokenType.NestedFieldDelimiter)] + [InlineData("/", ".", TokenType.NestedFieldDelimiter)] + [InlineData("/", "(", TokenType.FieldGroupStartDelimiter)] + [InlineData("/", "[", TokenType.FieldGroupStartDelimiter)] + [InlineData("/", ")", TokenType.FieldGroupEndDelimiter)] + [InlineData("/", "]", TokenType.FieldGroupEndDelimiter)] + [InlineData("/", ",", TokenType.FieldsDelimiter)] + [InlineData("/", "", TokenType.Eof)] + public void TheParseMethodShouldSetErrorIfIllegalTokenAfterNestedFieldDelimiter(string delimiter, string value, TokenType tokenType) { // Arrange - var source = new StringReader($"foo/{value}"); - var context = new ParserContext(source); + var source = new StringReader($"foo{delimiter}{value}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -108,7 +123,7 @@ public void TheParseMethodShouldParseMultipleIdentifiers() { // Arrange var source = new StringReader("foo,bar"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -119,16 +134,19 @@ public void TheParseMethodShouldParseMultipleIdentifiers() } [Theory] - [InlineData("/", TokenType.ForwardSlash)] - [InlineData("(", TokenType.LeftParenthesis)] - [InlineData(")", TokenType.RightParenthesis)] - [InlineData(",", TokenType.Comma)] + [InlineData("/", TokenType.NestedFieldDelimiter)] + [InlineData(".", TokenType.NestedFieldDelimiter)] + [InlineData("(", TokenType.FieldGroupStartDelimiter)] + [InlineData("[", TokenType.FieldGroupStartDelimiter)] + [InlineData(")", TokenType.FieldGroupEndDelimiter)] + [InlineData("]", TokenType.FieldGroupEndDelimiter)] + [InlineData(",", TokenType.FieldsDelimiter)] [InlineData("", TokenType.Eof)] public void TheParseMethodShouldSetErrorIfIllegalTokenAfterComma(string value, TokenType tokenType) { // Arrange var source = new StringReader($"foo,{value}"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -138,12 +156,14 @@ public void TheParseMethodShouldSetErrorIfIllegalTokenAfterComma(string value, T Assert.Equal(tokenType, context.Error.Type); } - [Fact] - public void TheParseMethodShouldParseGroupedIdentifier() + [Theory] + [InlineData("(", ")")] + [InlineData("[", "]")] + public void TheParseMethodShouldParseGroupedIdentifier(string leftGroupDelimiter, string rightGroupDelimiter) { // Arrange - var source = new StringReader("foo(bar)"); - var context = new ParserContext(source); + var source = new StringReader($"foo{leftGroupDelimiter}bar{rightGroupDelimiter}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -153,12 +173,14 @@ public void TheParseMethodShouldParseGroupedIdentifier() Assert.Equal(new[] { "foo/bar" }, context.Values.Select(value => string.Join("/", value.Parts))); } - [Fact] - public void TheParseMethodShouldParseGroupedMultipleIdentifiers() + [Theory] + [InlineData("(", ")")] + [InlineData("[", "]")] + public void TheParseMethodShouldParseGroupedMultipleIdentifiers(string leftGroupDelimiter, string rightGroupDelimiter) { // Arrange - var source = new StringReader("foo(bar,baz)"); - var context = new ParserContext(source); + var source = new StringReader($"foo{leftGroupDelimiter}bar,baz{rightGroupDelimiter}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -168,12 +190,14 @@ public void TheParseMethodShouldParseGroupedMultipleIdentifiers() Assert.Equal(new[] { "foo/bar", "foo/baz" }, context.Values.Select(value => string.Join("/", value.Parts))); } - [Fact] - public void TheParseMethodShouldParseIdentifierAfterGroupedMultipleIdentifiers() + [Theory] + [InlineData("(", ")")] + [InlineData("[", "]")] + public void TheParseMethodShouldParseIdentifierAfterGroupedMultipleIdentifiers(string leftGroupDelimiter, string rightGroupDelimiter) { // Arrange - var source = new StringReader("foo(bar,baz),qux"); - var context = new ParserContext(source); + var source = new StringReader($"foo{leftGroupDelimiter}bar,baz{rightGroupDelimiter},qux"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -183,12 +207,16 @@ public void TheParseMethodShouldParseIdentifierAfterGroupedMultipleIdentifiers() Assert.Equal(new[] { "foo/bar", "foo/baz", "qux" }, context.Values.Select(value => string.Join("/", value.Parts))); } - [Fact] - public void TheParseMethodShouldParseGroupedNestedIdentifier() + [Theory] + [InlineData("(", ")", "/")] + [InlineData("[", "]", "/")] + [InlineData("(", ")", ".")] + [InlineData("[", "]", ".")] + public void TheParseMethodShouldParseGroupedNestedIdentifier(string leftGroupDelimiter, string rightGroupDelimiter, string nestedFieldDelimiter) { // Arrange - var source = new StringReader("foo(bar/baz)"); - var context = new ParserContext(source); + var source = new StringReader($"foo{leftGroupDelimiter}bar{nestedFieldDelimiter}baz{rightGroupDelimiter}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -198,12 +226,16 @@ public void TheParseMethodShouldParseGroupedNestedIdentifier() Assert.Equal(new[] { "foo/bar/baz" }, context.Values.Select(value => string.Join("/", value.Parts))); } - [Fact] - public void TheParseMethodShouldParseGroupedMultipleNestedIdentifier() + [Theory] + [InlineData("(", ")", "/")] + [InlineData("[", "]", "/")] + [InlineData("(", ")", ".")] + [InlineData("[", "]", ".")] + public void TheParseMethodShouldParseGroupedMultipleNestedIdentifier(string leftGroupDelimiter, string rightGroupDelimiter, string nestedFieldDelimiter) { // Arrange - var source = new StringReader("foo(bar/baz,qux/quux)"); - var context = new ParserContext(source); + var source = new StringReader($"foo{leftGroupDelimiter}bar{nestedFieldDelimiter}baz,qux{nestedFieldDelimiter}quux{rightGroupDelimiter}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -213,12 +245,14 @@ public void TheParseMethodShouldParseGroupedMultipleNestedIdentifier() Assert.Equal(new[] { "foo/bar/baz", "foo/qux/quux" }, context.Values.Select(value => string.Join("/", value.Parts))); } - [Fact] - public void TheParseMethodShouldSetErrorIfTooManyLeftParenthesis() + [Theory] + [InlineData("(")] + [InlineData("[")] + public void TheParseMethodShouldSetErrorIfTooManyLeftDelimiters(string leftDelimiter) { // Arrange - var source = new StringReader("foo(bar"); - var context = new ParserContext(source); + var source = new StringReader($"foo{leftDelimiter}bar"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -228,32 +262,45 @@ public void TheParseMethodShouldSetErrorIfTooManyLeftParenthesis() Assert.Equal(TokenType.Eof, context.Error.Type); } - [Fact] - public void TheParseMethodShouldSetErrorIfTooManyRightParenthesis() + [Theory] + [InlineData(")")] + [InlineData("]")] + public void TheParseMethodShouldSetErrorIfTooManyRightDelimiters(string rightDelimiter) { // Arrange - var source = new StringReader("foo(bar))"); - var context = new ParserContext(source); + var source = new StringReader($"foo(bar{rightDelimiter}{rightDelimiter}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act parser.Parse(); // Assert - Assert.Equal(TokenType.RightParenthesis, context.Error.Type); + Assert.Equal(TokenType.FieldGroupEndDelimiter, context.Error.Type); } [Theory] - [InlineData("/", TokenType.ForwardSlash)] - [InlineData("(", TokenType.LeftParenthesis)] - [InlineData(")", TokenType.RightParenthesis)] - [InlineData(",", TokenType.Comma)] - [InlineData("", TokenType.Eof)] - public void TheParseMethodShouldSetErrorIfIllegalTokenAfterLeftParenthesis(string value, TokenType tokenType) + [InlineData("(", "/", TokenType.NestedFieldDelimiter)] + [InlineData("(", ".", TokenType.NestedFieldDelimiter)] + [InlineData("(", "(", TokenType.FieldGroupStartDelimiter)] + [InlineData("(", "[", TokenType.FieldGroupStartDelimiter)] + [InlineData("(", ")", TokenType.FieldGroupEndDelimiter)] + [InlineData("(", "]", TokenType.FieldGroupEndDelimiter)] + [InlineData("(", ",", TokenType.FieldsDelimiter)] + [InlineData("(", "", TokenType.Eof)] + [InlineData("[", "/", TokenType.NestedFieldDelimiter)] + [InlineData("[", ".", TokenType.NestedFieldDelimiter)] + [InlineData("[", "(", TokenType.FieldGroupStartDelimiter)] + [InlineData("[", "[", TokenType.FieldGroupStartDelimiter)] + [InlineData("[", ")", TokenType.FieldGroupEndDelimiter)] + [InlineData("[", "]", TokenType.FieldGroupEndDelimiter)] + [InlineData("[", ",", TokenType.FieldsDelimiter)] + [InlineData("[", "", TokenType.Eof)] + public void TheParseMethodShouldSetErrorIfIllegalTokenAfterLeftGroupDelimiter(string delimiter, string value, TokenType tokenType) { // Arrange - var source = new StringReader($"foo({value}"); - var context = new ParserContext(source); + var source = new StringReader($"foo{delimiter}{value}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -264,13 +311,13 @@ public void TheParseMethodShouldSetErrorIfIllegalTokenAfterLeftParenthesis(strin } [Theory] - [InlineData("/", TokenType.ForwardSlash)] - [InlineData("(", TokenType.LeftParenthesis)] - public void TheParseMethodShouldSetErrorIfIllegalTokenAfterRightParenthesis(string value, TokenType tokenType) + [InlineData(")", "/", TokenType.NestedFieldDelimiter)] + [InlineData("]", "(", TokenType.FieldGroupStartDelimiter)] + public void TheParseMethodShouldSetErrorIfIllegalTokenAfterRightGroupDelimiter(string delimiter, string value, TokenType tokenType) { // Arrange - var source = new StringReader($"foo(bar){value}"); - var context = new ParserContext(source); + var source = new StringReader($"foo(bar{delimiter}{value}"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -280,12 +327,14 @@ public void TheParseMethodShouldSetErrorIfIllegalTokenAfterRightParenthesis(stri Assert.Equal(tokenType, context.Error.Type); } - [Fact] - public void TheParseMethodShouldParseIdentifierAfterGroupedIdentifier() + [Theory] + [InlineData("(", ")")] + [InlineData("[", "]")] + public void TheParseMethodShouldParseIdentifierAfterGroupedIdentifier(string left, string right) { // Arrange - var source = new StringReader("foo(bar),baz"); - var context = new ParserContext(source); + var source = new StringReader($"foo{left}bar{right},baz"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -295,12 +344,14 @@ public void TheParseMethodShouldParseIdentifierAfterGroupedIdentifier() Assert.Equal(new[] { "foo/bar", "baz" }, context.Values.Select(value => string.Join("/", value.Parts))); } - [Fact] - public void TheParseMethodShouldParseIdentifierAfterNestedGroupedIdentifiers() + [Theory] + [InlineData("(", ")")] + [InlineData("[", "]")] + public void TheParseMethodShouldParseIdentifierAfterNestedGroupedIdentifiers(string left, string right) { // Arrange - var source = new StringReader("foo(bar(baz)),qux"); - var context = new ParserContext(source); + var source = new StringReader($"foo{left}bar{left}baz{right}{right},qux"); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -315,7 +366,7 @@ public void TheParseMethodShouldIgnoreSpace() { // Arrange var source = new StringReader(" foo"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -330,7 +381,7 @@ public void TheParseMethodShouldIgnoreTab() { // Arrange var source = new StringReader("\tfoo"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -345,7 +396,7 @@ public void TheParseMethodShouldIgnoreCarriageReturn() { // Arrange var source = new StringReader("\rfoo"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act @@ -360,7 +411,7 @@ public void TheParseMethodShouldIgnoreNewLine() { // Arrange var source = new StringReader("\nfoo"); - var context = new ParserContext(source); + var context = new ParserContext(source, DelimiterOptions.DefaultOptions); var parser = new Parser(context); // Act diff --git a/test/PartialResponse.Core.Tests/TokenTests.cs b/test/PartialResponse.Core.Tests/TokenTests.cs index 54bfced..c800c10 100644 --- a/test/PartialResponse.Core.Tests/TokenTests.cs +++ b/test/PartialResponse.Core.Tests/TokenTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using Xunit; diff --git a/test/PartialResponse.Core.Tests/TokenizerTests.cs b/test/PartialResponse.Core.Tests/TokenizerTests.cs index f7b599c..d2c7477 100644 --- a/test/PartialResponse.Core.Tests/TokenizerTests.cs +++ b/test/PartialResponse.Core.Tests/TokenizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using System; using System.IO; @@ -11,122 +11,82 @@ public class TokenizerTests [Fact] public void TheConstructorShouldThrowIfReaderIsNull() { - // Arrange - TextReader reader = null; - // Act - Assert.Throws("source", () => new Tokenizer(reader)); + Assert.Throws("source", () => new Tokenizer(null, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap)); } [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeForwardSlash() + public void TheConstructorShouldThrowIfDelimiterMapIsNull() { - // Arrange - var reader = new StringReader("/"); - var tokenizer = new Tokenizer(reader); - // Act - var token = tokenizer.NextToken(); - - // Assert - Assert.Equal(TokenType.ForwardSlash, token.Type); - } - - [Fact] - public void TheNextTokenMethodShouldReturnTokenValueForwardSlash() - { - // Arrange - var reader = new StringReader("/"); - var tokenizer = new Tokenizer(reader); - - // Act - var token = tokenizer.NextToken(); - - // Assert - Assert.Equal("/", token.Value); + Assert.Throws("tokensMap", () => new Tokenizer(new StringReader(string.Empty), null)); } - [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeLeftParenthesis() + [Theory] + [InlineData("/")] + [InlineData(".")] + public void TheNextTokenMethodShouldReturnTokenTypeAndValueNestedFieldDelimiter(string data) { // Arrange - var reader = new StringReader("("); - var tokenizer = new Tokenizer(reader); + var reader = new StringReader(data); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert - Assert.Equal(TokenType.LeftParenthesis, token.Type); + Assert.Equal(TokenType.NestedFieldDelimiter, token.Type); + Assert.Equal(data, token.Value); } - [Fact] - public void TheNextTokenMethodShouldReturnTokenValueLeftParenthesis() + [Theory] + [InlineData("(")] + [InlineData("[")] + public void TheNextTokenMethodShouldReturnTokenTypeAndValueStartGroupDelimiter(string data) { // Arrange - var reader = new StringReader("("); - var tokenizer = new Tokenizer(reader); + var reader = new StringReader(data); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert - Assert.Equal("(", token.Value); - } + Assert.Equal(TokenType.FieldGroupStartDelimiter, token.Type); - [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeRightParenthesis() - { - // Arrange - var reader = new StringReader(")"); - var tokenizer = new Tokenizer(reader); - - // Act - var token = tokenizer.NextToken(); - - // Assert - Assert.Equal(TokenType.RightParenthesis, token.Type); + Assert.Equal(data, token.Value); } - [Fact] - public void TheNextTokenMethodShouldReturnTokenValueRightParenthesis() + [Theory] + [InlineData(")")] + [InlineData("]")] + public void TheNextTokenMethodShouldReturnTokenTypeAndValueEndGroupDelimiter(string data) { // Arrange - var reader = new StringReader(")"); - var tokenizer = new Tokenizer(reader); + var reader = new StringReader(data); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert - Assert.Equal(")", token.Value); - } - - [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeComma() - { - // Arrange - var reader = new StringReader(","); - var tokenizer = new Tokenizer(reader); + Assert.Equal(TokenType.FieldGroupEndDelimiter, token.Type); - // Act - var token = tokenizer.NextToken(); - - // Assert - Assert.Equal(TokenType.Comma, token.Type); + Assert.Equal(data, token.Value); } [Fact] - public void TheNextTokenMethodShouldReturnTokenValueComma() + public void TheNextTokenMethodShouldReturnTokenTypeAndValueFieldsDelimiter() { // Arrange var reader = new StringReader(","); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert + Assert.Equal(TokenType.FieldsDelimiter, token.Type); Assert.Equal(",", token.Value); } @@ -135,62 +95,32 @@ public void TheNextTokenMethodShouldReturnTokenValueComma() [InlineData("\t")] [InlineData("\r")] [InlineData("\n")] - public void TheNextTokenMethodShouldReturnTokenTypeWhiteSpace(string value) + public void TheNextTokenMethodShouldReturnTokenTypeAndValueWhiteSpace(string value) { // Arrange var reader = new StringReader(value); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert Assert.Equal(TokenType.WhiteSpace, token.Type); - } - - [Theory] - [InlineData(" ")] - [InlineData("\t")] - [InlineData("\r")] - [InlineData("\n")] - public void TheNextTokenMethodShouldReturnTokenValueWhiteSpace(string value) - { - // Arrange - var reader = new StringReader(value); - var tokenizer = new Tokenizer(reader); - - // Act - var token = tokenizer.NextToken(); - - // Assert Assert.Equal(value, token.Value); } [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeIdentifier() + public void TheNextTokenMethodShouldReturnTokenTypeAndValueIdentifier() { // Arrange var reader = new StringReader("foo"); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert Assert.Equal(TokenType.Identifier, token.Type); - } - - [Fact] - public void TheNextTokenMethodShouldReturnTokenValueIdentifier() - { - // Arrange - var reader = new StringReader("foo"); - var tokenizer = new Tokenizer(reader); - - // Act - var token = tokenizer.NextToken(); - - // Assert Assert.Equal("foo", token.Value); } @@ -199,7 +129,7 @@ public void TheNextTokenMethodShouldReturnTokenTypeEofIfEndReached() { // Arrange var reader = new StringReader("foo"); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); tokenizer.NextToken(); @@ -211,55 +141,28 @@ public void TheNextTokenMethodShouldReturnTokenTypeEofIfEndReached() } [Fact] - public void TheNextTokenMethodShouldReturnTokenValueIdentifierBeforeForwardSlash() - { - // Arrange - var reader = new StringReader("foo/"); - var tokenizer = new Tokenizer(reader); - - // Act - var token = tokenizer.NextToken(); - - // Assert - Assert.Equal("foo", token.Value); - } - - [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeIdentifierBeforeForwardSlash() + public void TheNextTokenMethodShouldReturnTokenTypeAndValueIdentifierBeforeForwardSlash() { // Arrange var reader = new StringReader("foo/"); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert Assert.Equal(TokenType.Identifier, token.Type); - } - - [Fact] - public void TheNextTokenMethodShouldReturnTokenValueIdentifierAfterForwardSlash() - { - // Arrange - var reader = new StringReader("/foo"); - var tokenizer = new Tokenizer(reader); - - tokenizer.NextToken(); - - // Act - var token = tokenizer.NextToken(); - - // Assert Assert.Equal("foo", token.Value); } - [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeIdentifierAfterForwardSlash() + [Theory] + [InlineData("/")] + [InlineData(".")] + public void TheNextTokenMethodShouldReturnTokenTypeAndValueIdentifierAfterNestedFieldDelimiter(string delimiter) { // Arrange - var reader = new StringReader("/foo"); - var tokenizer = new Tokenizer(reader); + var reader = new StringReader($"{delimiter}foo"); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); tokenizer.NextToken(); @@ -268,58 +171,30 @@ public void TheNextTokenMethodShouldReturnTokenTypeIdentifierAfterForwardSlash() // Assert Assert.Equal(TokenType.Identifier, token.Type); - } - - [Fact] - public void TheNextTokenMethodShouldReturnTokenValueIdentifierBeforeWhiteSpace() - { - // Arrange - var reader = new StringReader("foo "); - var tokenizer = new Tokenizer(reader); - - // Act - var token = tokenizer.NextToken(); - - // Assert Assert.Equal("foo", token.Value); } [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeIdentifierBeforeWhiteSpace() + public void TheNextTokenMethodShouldReturnTokenTypeAndValueIdentifierBeforeWhiteSpace() { // Arrange var reader = new StringReader("foo "); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); // Act var token = tokenizer.NextToken(); // Assert Assert.Equal(TokenType.Identifier, token.Type); - } - - [Fact] - public void TheNextTokenMethodShouldReturnTokenValueIdentifierAfterWhiteSpace() - { - // Arrange - var reader = new StringReader(" foo"); - var tokenizer = new Tokenizer(reader); - - tokenizer.NextToken(); - - // Act - var token = tokenizer.NextToken(); - - // Assert Assert.Equal("foo", token.Value); } [Fact] - public void TheNextTokenMethodShouldReturnTokenTypeIdentifierAfterWhiteSpace() + public void TheNextTokenMethodShouldReturnTokenTypeAndValueIdentifierAfterWhiteSpace() { // Arrange var reader = new StringReader(" foo"); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); tokenizer.NextToken(); @@ -328,6 +203,7 @@ public void TheNextTokenMethodShouldReturnTokenTypeIdentifierAfterWhiteSpace() // Assert Assert.Equal(TokenType.Identifier, token.Type); + Assert.Equal("foo", token.Value); } [Fact] @@ -335,7 +211,7 @@ public void TheNextTokenMethodShouldReturnTokenPosition() { // Arrange var reader = new StringReader("foo/"); - var tokenizer = new Tokenizer(reader); + var tokenizer = new Tokenizer(reader, DelimiterOptions.DefaultOptions.DelimiterToTokenTypeMap); tokenizer.NextToken(); diff --git a/test/PartialResponse.Core.Tests/UnexpectedTokenErrorTests.cs b/test/PartialResponse.Core.Tests/UnexpectedTokenErrorTests.cs index 0079fd2..0cfacc6 100644 --- a/test/PartialResponse.Core.Tests/UnexpectedTokenErrorTests.cs +++ b/test/PartialResponse.Core.Tests/UnexpectedTokenErrorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Arjen Post. See LICENSE in the project root for license information. +// Copyright (c) Arjen Post and contributors. See LICENSE in the project root for license information. using Xunit;