public static void Ctor_Validating_FromDelegate()
        {
            object expectedState = new object();
            SpanAction <byte, object> spanAction = (span, actualState) =>
            {
                Assert.Same(expectedState, actualState);
                Assert.NotEqual(0, span.Length); // shouldn't have been called for a zero-length span

                for (int i = 0; i < span.Length; i++)
                {
                    Assert.Equal(0, span[i]);         // should've been zero-inited
                    span[i] = (byte)('a' + (i % 26)); // writes "abc...xyzabc...xyz..."
                }
            };

            ArgumentException exception = Assert.Throws <ArgumentOutOfRangeException>(() => Utf8String.Create(-1, expectedState, spanAction));

            Assert.Equal("length", exception.ParamName);

            exception = Assert.Throws <ArgumentNullException>(() => Utf8String.Create(10, expectedState, action: null));
            Assert.Equal("action", exception.ParamName);

            Assert.Same(Utf8String.Empty, Utf8String.Create(0, expectedState, spanAction));

            Assert.Equal(u8("abcde"), Utf8String.Create(5, expectedState, spanAction));
        }
        public static void Ctor_Validating_FromDelegate_ThrowsIfDelegateProvidesInvalidData()
        {
            SpanAction <byte, object> spanAction = (span, actualState) =>
            {
                span[0] = 0xFF; // never a valid UTF-8 byte
            };

            Assert.Throws <ArgumentException>(() => Utf8String.Create(10, new object(), spanAction));
        }
        private static unsafe Utf8String MakeStringFrom(ReadOnlySpan <byte> input, ref byte[] borrowedArray, bool isQueryString)
        {
            // If there are any %xx characters in the string, unescape them now.

            int indexOfPercentChar = input.IndexOf((byte)'%');

            if (indexOfPercentChar >= 0)
            {
                if (borrowedArray == null)
                {
                    borrowedArray = ArrayPool <byte> .Shared.Rent(input.Length);
                }
                else if (borrowedArray.Length < input.Length)
                {
                    ArrayPool <byte> .Shared.Return(borrowedArray);

                    borrowedArray = ArrayPool <byte> .Shared.Rent(input.Length);
                }

                ReadOnlySpan <byte> slicedInput = input;
                int borrowedArrayOffset         = 0;

                do
                {
                    // Copy everything up until the % character we saw

                    slicedInput.Slice(0, indexOfPercentChar).CopyTo(borrowedArray.AsSpan(borrowedArrayOffset));
                    slicedInput          = slicedInput.Slice(indexOfPercentChar);
                    borrowedArrayOffset += indexOfPercentChar;

                    // Attempt percent-unescaping

                    int byteValue = PercentUnescape(slicedInput);
                    if (byteValue < 0)
                    {
                        // Not a valid %xx escape sequence - skip it
                        borrowedArray[borrowedArrayOffset++] = (byte)'%';
                        slicedInput = slicedInput.Slice(1);
                    }
                    else
                    {
                        Debug.Assert(byteValue <= byte.MaxValue);
                        borrowedArray[borrowedArrayOffset++] = (byte)byteValue;
                        slicedInput = slicedInput.Slice(3);
                    }

                    indexOfPercentChar = slicedInput.IndexOf((byte)'%'); // and search again
                } while (indexOfPercentChar >= 0);

                // If there's any leftover data, copy it now, then point the input span to the rented buffer

                slicedInput.CopyTo(borrowedArray.AsSpan(borrowedArrayOffset));
                input = borrowedArray.AsSpan(0, borrowedArrayOffset + slicedInput.Length);
            }

            if (isQueryString)
            {
                // Use pointers to smuggle the ref struct span across a closure
                fixed(byte *pInput = input)
                {
                    return(Utf8String.Create(input.Length, (IntPtr)pInput, (span, state) =>
                    {
                        new Span <byte>((byte *)state, span.Length).CopyTo(span);

                        // replace '+' with ' '
                        while (!span.IsEmpty)
                        {
                            int idxOfChar = span.IndexOf((byte)'+');
                            if (idxOfChar < 0)
                            {
                                break;
                            }
                            span[idxOfChar] = (byte)' ';
                            span = span.Slice(idxOfChar + 1);
                        }
                    }));
                }
            }
            else
            {
                // no need to replace '+' with ' '
                return(new Utf8String(input));
            }
        }
 public StateProperty(string name) : this(name, Utf8String.Create(name))
 {
 }