Merge pull request #1848 from smhdfdl/id-and-ref

Fix issue 1843 - support Id keyword
diff --git a/bin/unittestschema/idandref.json b/bin/unittestschema/idandref.json
new file mode 100644
index 0000000..ad485d2
--- /dev/null
+++ b/bin/unittestschema/idandref.json
@@ -0,0 +1,69 @@
+{
+  "id": "http://example.com/root.json",
+  "definitions": {
+    "A": {
+      "id": "#foo",
+      "type": "integer"
+    },
+    "B": {
+      "id": "other.json",
+      "definitions": {
+        "X": {
+          "id": "#bar",
+          "type": "boolean"
+        },
+        "Y": {
+          "$ref": "#/definitions/X"
+        },
+        "W": {
+          "$ref": "#/definitions/Y"
+        },
+        "Z": {
+          "$ref": "#bar"
+        },
+        "N": {
+          "properties": {
+            "NX": {
+              "$ref": "#/definitions/X"
+            }
+          }
+        }
+      }
+    }
+  },
+  "properties": {
+    "PA1": {
+      "$ref": "http://example.com/root.json#/definitions/A"
+    },
+    "PA2": {
+      "$ref": "#/definitions/A"
+    },
+    "PA3": {
+      "$ref": "#foo"
+    },
+    "PX1": {
+      "$ref": "#/definitions/B/definitions/X"
+    },
+    "PX2Y": {
+      "$ref": "#/definitions/B/definitions/Y"
+    },
+    "PX3Z": {
+      "$ref": "#/definitions/B/definitions/Z"
+    },
+    "PX4": {
+      "$ref": "http://example.com/other.json#/definitions/X"
+    },
+    "PX5": {
+      "$ref": "other.json#/definitions/X"
+    },
+    "PX6": {
+      "$ref": "other.json#bar"
+    },
+    "PX7W": {
+      "$ref": "#/definitions/B/definitions/W"
+    },
+    "PX8N": {
+      "$ref": "#/definitions/B/definitions/N"
+    }
+  }
+}
\ No newline at end of file
diff --git a/example/schemavalidator/schemavalidator.cpp b/example/schemavalidator/schemavalidator.cpp
index bffd64a..8c7e26c 100644
--- a/example/schemavalidator/schemavalidator.cpp
+++ b/example/schemavalidator/schemavalidator.cpp
@@ -2,6 +2,8 @@
 
 // The example validates JSON text from stdin with a JSON schema specified in the argument.
 
+#define RAPIDJSON_HAS_STDSTRING 1
+
 #include "rapidjson/error/en.h"
 #include "rapidjson/filereadstream.h"
 #include "rapidjson/schema.h"
diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h
index 057b37a..e2cc600 100644
--- a/include/rapidjson/document.h
+++ b/include/rapidjson/document.h
@@ -1951,7 +1951,7 @@
         case kArrayType:
             if (RAPIDJSON_UNLIKELY(!handler.StartArray()))
                 return false;
-            for (const GenericValue* v = Begin(); v != End(); ++v)
+            for (ConstValueIterator v = Begin(); v != End(); ++v)
                 if (RAPIDJSON_UNLIKELY(!v->Accept(handler)))
                     return false;
             return handler.EndArray(data_.a.size);
diff --git a/include/rapidjson/internal/strfunc.h b/include/rapidjson/internal/strfunc.h
index baecb6c..b698a8f 100644
--- a/include/rapidjson/internal/strfunc.h
+++ b/include/rapidjson/internal/strfunc.h
@@ -45,6 +45,20 @@
     return SizeType(std::wcslen(s));
 }
 
+//! Custom strcmpn() which works on different character types.
+/*! \tparam Ch Character type (e.g. char, wchar_t, short)
+    \param s1 Null-terminated input string.
+    \param s2 Null-terminated input string.
+    \return 0 if equal
+*/
+template<typename Ch>
+inline int StrCmp(const Ch* s1, const Ch* s2) {
+    RAPIDJSON_ASSERT(s1 != 0);
+    RAPIDJSON_ASSERT(s2 != 0);
+    while(*s1 && (*s1 == *s2)) { s1++; s2++; }
+    return static_cast<unsigned>(*s1) < static_cast<unsigned>(*s2) ? -1 : static_cast<unsigned>(*s1) > static_cast<unsigned>(*s2);
+}
+
 //! Returns number of code points in a encoded string.
 template<typename Encoding>
 bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) {
diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h
index 9621442..67a9cb0 100644
--- a/include/rapidjson/pointer.h
+++ b/include/rapidjson/pointer.h
@@ -16,6 +16,7 @@
 #define RAPIDJSON_POINTER_H_
 
 #include "document.h"
+#include "uri.h"
 #include "internal/itoa.h"
 
 #ifdef __clang__
@@ -80,6 +81,8 @@
 public:
     typedef typename ValueType::EncodingType EncodingType;  //!< Encoding type from Value
     typedef typename ValueType::Ch Ch;                      //!< Character type from Value
+    typedef GenericUri<ValueType, Allocator> UriType;
+
 
     //! A token is the basic units of internal representation.
     /*!
@@ -520,6 +523,70 @@
 
     //@}
 
+    //!@name Compute URI
+    //@{
+
+    //! Compute the in-scope URI for a subtree.
+    //  For use with JSON pointers into JSON schema documents.
+    /*!
+        \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root.
+        \param rootUri Root URI
+        \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token.
+        \param allocator Allocator for Uris
+        \return Uri if it can be resolved. Otherwise null.
+
+        \note
+        There are only 3 situations when a URI cannot be resolved:
+        1. A value in the path is not an array nor object.
+        2. An object value does not contain the token.
+        3. A token is out of range of an array value.
+
+        Use unresolvedTokenIndex to retrieve the token index.
+    */
+    UriType GetUri(ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0, Allocator* allocator = 0) const {
+        static const Ch kIdString[] = { 'i', 'd', '\0' };
+        static const ValueType kIdValue(kIdString, 2);
+        UriType base = UriType(rootUri, allocator);
+        RAPIDJSON_ASSERT(IsValid());
+        ValueType* v = &root;
+        for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) {
+            switch (v->GetType()) {
+                case kObjectType:
+                {
+                    // See if we have an id, and if so resolve with the current base
+                    typename ValueType::MemberIterator m = v->FindMember(kIdValue);
+                    if (m != v->MemberEnd() && (m->value).IsString()) {
+                        UriType here = UriType(m->value, allocator).Resolve(base, allocator);
+                        base = here;
+                    }
+                    m = v->FindMember(GenericValue<EncodingType>(GenericStringRef<Ch>(t->name, t->length)));
+                    if (m == v->MemberEnd())
+                        break;
+                    v = &m->value;
+                }
+                  continue;
+                case kArrayType:
+                    if (t->index == kPointerInvalidIndex || t->index >= v->Size())
+                        break;
+                    v = &((*v)[t->index]);
+                    continue;
+                default:
+                    break;
+            }
+
+            // Error: unresolved token
+            if (unresolvedTokenIndex)
+                *unresolvedTokenIndex = static_cast<size_t>(t - tokens_);
+            return UriType(allocator);
+        }
+        return base;
+    }
+
+    UriType GetUri(const ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0, Allocator* allocator = 0) const {
+      return GetUri(const_cast<ValueType&>(root), rootUri, unresolvedTokenIndex, allocator);
+    }
+
+
     //!@name Query value
     //@{
 
diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h
index a034021..f0759ff 100644
--- a/include/rapidjson/schema.h
+++ b/include/rapidjson/schema.h
@@ -19,6 +19,7 @@
 #include "pointer.h"
 #include "stringbuffer.h"
 #include "error/en.h"
+#include "uri.h"
 #include <cmath> // abs, floor
 
 #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX)
@@ -432,11 +433,13 @@
     typedef Schema<SchemaDocumentType> SchemaType;
     typedef GenericValue<EncodingType, AllocatorType> SValue;
     typedef IValidationErrorHandler<Schema> ErrorHandler;
+    typedef GenericUri<ValueType, AllocatorType> UriType;
     friend class GenericSchemaDocument<ValueType, AllocatorType>;
 
-    Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) :
+    Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) :
         allocator_(allocator),
         uri_(schemaDocument->GetURI(), *allocator),
+        id_(id),
         pointer_(p, allocator),
         typeless_(schemaDocument->GetTypeless()),
         enum_(),
@@ -474,9 +477,28 @@
         typedef typename ValueType::ConstValueIterator ConstValueIterator;
         typedef typename ValueType::ConstMemberIterator ConstMemberIterator;
 
+        // PR #1393
+        // Early add this Schema and its $ref(s) in schemaDocument's map to avoid infinite
+        // recursion (with recursive schemas), since schemaDocument->getSchema() is always
+        // checked before creating a new one. Don't cache typeless_, though.
+        if (this != typeless_) {
+          typedef typename SchemaDocumentType::SchemaEntry SchemaEntry;
+          SchemaEntry *entry = schemaDocument->schemaMap_.template Push<SchemaEntry>();
+          new (entry) SchemaEntry(pointer_, this, true, allocator_);
+          schemaDocument->AddSchemaRefs(this);
+        }
+
         if (!value.IsObject())
             return;
 
+        // If we have an id property, resolve it with the in-scope id
+        if (const ValueType* v = GetMember(value, GetIdString())) {
+            if (v->IsString()) {
+                UriType local(*v, allocator);
+                id_ = local.Resolve(id_, allocator);
+            }
+        }
+
         if (const ValueType* v = GetMember(value, GetTypeString())) {
             type_ = 0;
             if (v->IsString())
@@ -506,9 +528,9 @@
             AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document);
 
             if (const ValueType* v = GetMember(value, GetNotString())) {
-            schemaDocument->CreateSchema(&not_, p.Append(GetNotString(), allocator_), *v, document);
-            notValidatorIndex_ = validatorCount_;
-            validatorCount_++;
+                schemaDocument->CreateSchema(&not_, p.Append(GetNotString(), allocator_), *v, document, id_);
+                notValidatorIndex_ = validatorCount_;
+                validatorCount_++;
             }
         }
 
@@ -524,7 +546,7 @@
             if (properties && properties->IsObject())
                 for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr)
                     AddUniqueElement(allProperties, itr->name);
-            
+
             if (required && required->IsArray())
                 for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr)
                     if (itr->IsString())
@@ -555,7 +577,7 @@
             for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) {
                 SizeType index;
                 if (FindPropertyIndex(itr->name, &index))
-                    schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document);
+                    schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document, id_);
             }
         }
 
@@ -567,7 +589,7 @@
             for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) {
                 new (&patternProperties_[patternPropertyCount_]) PatternProperty();
                 patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name);
-                schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document);
+                schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document, id_);
                 patternPropertyCount_++;
             }
         }
@@ -599,7 +621,7 @@
                     }
                     else if (itr->value.IsObject()) {
                         hasSchemaDependencies_ = true;
-                        schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document);
+                        schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document, id_);
                         properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_;
                         validatorCount_++;
                     }
@@ -611,7 +633,7 @@
             if (v->IsBool())
                 additionalProperties_ = v->GetBool();
             else if (v->IsObject())
-                schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document);
+                schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document, id_);
         }
 
         AssignIfExist(minProperties_, value, GetMinPropertiesString());
@@ -621,12 +643,12 @@
         if (const ValueType* v = GetMember(value, GetItemsString())) {
             PointerType q = p.Append(GetItemsString(), allocator_);
             if (v->IsObject()) // List validation
-                schemaDocument->CreateSchema(&itemsList_, q, *v, document);
+                schemaDocument->CreateSchema(&itemsList_, q, *v, document, id_);
             else if (v->IsArray()) { // Tuple validation
                 itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size()));
                 SizeType index = 0;
                 for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++)
-                    schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document);
+                    schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document, id_);
             }
         }
 
@@ -637,7 +659,7 @@
             if (v->IsBool())
                 additionalItems_ = v->GetBool();
             else if (v->IsObject())
-                schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document);
+                schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document, id_);
         }
 
         AssignIfExist(uniqueItems_, value, GetUniqueItemsString());
@@ -697,6 +719,10 @@
         return uri_;
     }
 
+    const UriType& GetId() const {
+        return id_;
+    }
+
     const PointerType& GetPointer() const {
         return pointer_;
     }
@@ -776,7 +802,7 @@
             foundEnum:;
         }
 
-    // Only check allOf etc if we have validators
+        // Only check allOf etc if we have validators
         if (context.validatorCount > 0) {
             if (allOf_.schemas)
                 for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++)
@@ -826,7 +852,7 @@
         }
         return CreateParallelValidator(context);
     }
-    
+
     bool Bool(Context& context, bool) const {
         if (!(type_ & (1 << kBooleanSchemaType))) {
             DisallowedType(context, GetBooleanString());
@@ -870,13 +896,13 @@
 
         if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d))
             return false;
-        
+
         if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d))
             return false;
-        
+
         return CreateParallelValidator(context);
     }
-    
+
     bool String(Context& context, const Ch* str, SizeType length, bool) const {
         if (!(type_ & (1 << kStringSchemaType))) {
             DisallowedType(context, GetStringString());
@@ -925,7 +951,7 @@
 
         return CreateParallelValidator(context);
     }
-    
+
     bool Key(Context& context, const Ch* str, SizeType len, bool) const {
         if (patternProperties_) {
             context.patternPropertiesSchemaCount = 0;
@@ -1018,7 +1044,7 @@
                 }
             }
             if (context.error_handler.EndDependencyErrors())
-                RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies);  
+                RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies);
         }
 
         return true;
@@ -1038,12 +1064,12 @@
 
     bool EndArray(Context& context, SizeType elementCount) const {
         context.inArray = false;
-        
+
         if (elementCount < minItems_) {
             context.error_handler.TooFewItems(elementCount, minItems_);
             RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems);
         }
-        
+
         if (elementCount > maxItems_) {
             context.error_handler.TooManyItems(elementCount, maxItems_);
             RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems);
@@ -1132,6 +1158,15 @@
     RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm')
     RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f')
     RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't')
+    RAPIDJSON_STRING_(Ref, '$', 'r', 'e', 'f')
+    RAPIDJSON_STRING_(Id, 'i', 'd')
+
+    RAPIDJSON_STRING_(SchemeEnd, ':')
+    RAPIDJSON_STRING_(AuthStart, '/', '/')
+    RAPIDJSON_STRING_(QueryStart, '?')
+    RAPIDJSON_STRING_(FragStart, '#')
+    RAPIDJSON_STRING_(Slash, '/')
+    RAPIDJSON_STRING_(Dot, '.')
 
 #undef RAPIDJSON_STRING_
 
@@ -1197,7 +1232,7 @@
                 out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*)));
                 memset(out.schemas, 0, sizeof(Schema*)* out.count);
                 for (SizeType i = 0; i < out.count; i++)
-                    schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document);
+                    schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document, id_);
                 out.begin = validatorCount_;
                 validatorCount_ += out.count;
             }
@@ -1274,10 +1309,10 @@
 
             if (anyOf_.schemas)
                 CreateSchemaValidators(context, anyOf_, false);
-            
+
             if (oneOf_.schemas)
                 CreateSchemaValidators(context, oneOf_, false);
-            
+
             if (not_)
                 context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false);
 
@@ -1301,7 +1336,7 @@
         SizeType len = name.GetStringLength();
         const Ch* str = name.GetString();
         for (SizeType index = 0; index < propertyCount_; index++)
-            if (properties_[index].name.GetStringLength() == len && 
+            if (properties_[index].name.GetStringLength() == len &&
                 (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0))
             {
                 *outIndex = index;
@@ -1462,7 +1497,7 @@
 
     struct PatternProperty {
         PatternProperty() : schema(), pattern() {}
-        ~PatternProperty() { 
+        ~PatternProperty() {
             if (pattern) {
                 pattern->~RegexType();
                 AllocatorType::Free(pattern);
@@ -1474,6 +1509,7 @@
 
     AllocatorType* allocator_;
     SValue uri_;
+    UriType id_;
     PointerType pointer_;
     const SchemaType* typeless_;
     uint64_t* enum_;
@@ -1516,7 +1552,7 @@
     SValue multipleOf_;
     bool exclusiveMinimum_;
     bool exclusiveMaximum_;
-    
+
     SizeType defaultValueLength_;
 };
 
@@ -1559,9 +1595,12 @@
 class IGenericRemoteSchemaDocumentProvider {
 public:
     typedef typename SchemaDocumentType::Ch Ch;
+    typedef typename SchemaDocumentType::ValueType ValueType;
+    typedef typename SchemaDocumentType::AllocatorType AllocatorType;
 
     virtual ~IGenericRemoteSchemaDocumentProvider() {}
     virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0;
+    virtual const SchemaDocumentType* GetRemoteDocument(GenericUri<ValueType, AllocatorType> uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); }
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1586,7 +1625,8 @@
     typedef typename EncodingType::Ch Ch;
     typedef internal::Schema<GenericSchemaDocument> SchemaType;
     typedef GenericPointer<ValueType, Allocator> PointerType;
-    typedef GenericValue<EncodingType, Allocator> URIType;
+    typedef GenericValue<EncodingType, AllocatorType> SValue;
+    typedef GenericUri<ValueType, Allocator> UriType;
     friend class internal::Schema<GenericSchemaDocument>;
     template <typename, typename, typename>
     friend class GenericSchemaValidator;
@@ -1600,9 +1640,11 @@
         \param uriLength Length of \c name, in code points.
         \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null.
         \param allocator An optional allocator instance for allocating memory. Can be null.
+        \param pointer An optional JSON pointer to the start of the schema document
     */
     explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0,
-        IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) :
+        IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0,
+        const PointerType& pointer = PointerType()) :  // PR #1393
         remoteProvider_(remoteProvider),
         allocator_(allocator),
         ownAllocator_(),
@@ -1616,30 +1658,20 @@
 
         Ch noUri[1] = {0};
         uri_.SetString(uri ? uri : noUri, uriLength, *allocator_);
+        docId_ = UriType(uri_, allocator_);
 
         typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType)));
-        new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_);
+        new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_);
 
         // Generate root schema, it will call CreateSchema() to create sub-schemas,
-        // And call AddRefSchema() if there are $ref.
-        CreateSchemaRecursive(&root_, PointerType(), document, document);
-
-        // Resolve $ref
-        while (!schemaRef_.Empty()) {
-            SchemaRefEntry* refEntry = schemaRef_.template Pop<SchemaRefEntry>(1);
-            if (const SchemaType* s = GetSchema(refEntry->target)) {
-                if (refEntry->schema)
-                    *refEntry->schema = s;
-
-                // Create entry in map if not exist
-                if (!GetSchema(refEntry->source)) {
-                    new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(refEntry->source, const_cast<SchemaType*>(s), false, allocator_);
-                }
-            }
-            else if (refEntry->schema)
-                *refEntry->schema = typeless_;
-
-            refEntry->~SchemaRefEntry();
+        // And call HandleRefSchema() if there are $ref.
+        // PR #1393 use input pointer if supplied
+        root_ = typeless_;
+        if (pointer.GetTokenCount() == 0) {
+            CreateSchemaRecursive(&root_, pointer, document, document, docId_);
+        }
+        else if (const ValueType* v = pointer.Get(document)) {
+            CreateSchema(&root_, pointer, *v, document, docId_);
         }
 
         RAPIDJSON_ASSERT(root_ != 0);
@@ -1657,7 +1689,8 @@
         typeless_(rhs.typeless_),
         schemaMap_(std::move(rhs.schemaMap_)),
         schemaRef_(std::move(rhs.schemaRef_)),
-        uri_(std::move(rhs.uri_))
+        uri_(std::move(rhs.uri_)),
+        docId_(rhs.docId_)
     {
         rhs.remoteProvider_ = 0;
         rhs.allocator_ = 0;
@@ -1679,7 +1712,7 @@
         RAPIDJSON_DELETE(ownAllocator_);
     }
 
-    const URIType& GetURI() const { return uri_; }
+    const SValue& GetURI() const { return uri_; }
 
     //! Get the root schema.
     const SchemaType& GetRoot() const { return *root_; }
@@ -1690,12 +1723,7 @@
     //! Prohibit assignment
     GenericSchemaDocument& operator=(const GenericSchemaDocument&);
 
-    struct SchemaRefEntry {
-        SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {}
-        PointerType source;
-        PointerType target;
-        const SchemaType** schema;
-    };
+    typedef const PointerType* SchemaRefPtr; // PR #1393
 
     struct SchemaEntry {
         SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {}
@@ -1710,79 +1738,197 @@
         bool owned;
     };
 
-    void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) {
-        if (schema)
-            *schema = typeless_;
-
+    // Changed by PR #1393
+    void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
         if (v.GetType() == kObjectType) {
-            const SchemaType* s = GetSchema(pointer);
-            if (!s)
-                CreateSchema(schema, pointer, v, document);
+            UriType newid = UriType(CreateSchema(schema, pointer, v, document, id), allocator_);
 
             for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr)
-                CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document);
+                CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, newid);
         }
         else if (v.GetType() == kArrayType)
             for (SizeType i = 0; i < v.Size(); i++)
-                CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document);
+                CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document, id);
     }
 
-    void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) {
+    // Changed by PR #1393
+    const UriType& CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
         RAPIDJSON_ASSERT(pointer.IsValid());
         if (v.IsObject()) {
-            if (!HandleRefSchema(pointer, schema, v, document)) {
-                SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_);
-                new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(pointer, s, true, allocator_);
+            if (const SchemaType* sc = GetSchema(pointer)) {
+                if (schema)
+                    *schema = sc;
+                AddSchemaRefs(const_cast<SchemaType*>(sc));
+            }
+            else if (!HandleRefSchema(pointer, schema, v, document, id)) {
+                // The new schema constructor adds itself and its $ref(s) to schemaMap_
+                SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_, id);
                 if (schema)
                     *schema = s;
+                return s->GetId();
             }
         }
+        else {
+            if (schema)
+                *schema = typeless_;
+            AddSchemaRefs(typeless_);
+        }
+        return id;
     }
 
-    bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) {
-        static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' };
-        static const ValueType kRefValue(kRefString, 4);
-
-        typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue);
+    // Changed by PR #1393
+    // TODO should this return a UriType& ?
+    bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document, const UriType& id) {
+        typename ValueType::ConstMemberIterator itr = v.FindMember(SchemaType::GetRefString());
         if (itr == v.MemberEnd())
             return false;
 
+        // Resolve the source pointer to the $ref'ed schema (finally)
+        new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source);
+
         if (itr->value.IsString()) {
             SizeType len = itr->value.GetStringLength();
             if (len > 0) {
-                const Ch* s = itr->value.GetString();
-                SizeType i = 0;
-                while (i < len && s[i] != '#') // Find the first #
-                    i++;
-
-                if (i > 0) { // Remote reference, resolve immediately
+                // First resolve $ref against the in-scope id
+                UriType scopeId = UriType(id, allocator_);
+                UriType ref = UriType(itr->value, allocator_).Resolve(scopeId, allocator_);
+                // See if the resolved $ref minus the fragment matches a resolved id in this document
+                // Search from the root. Returns the subschema in the document and its absolute JSON pointer.
+                PointerType basePointer = PointerType();
+                const ValueType *base = FindId(document, ref, basePointer, docId_, false);
+                if (!base) {
+                    // Remote reference - call the remote document provider
                     if (remoteProvider_) {
-                        if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) {
-                            PointerType pointer(&s[i], len - i, allocator_);
-                            if (pointer.IsValid()) {
-                                if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) {
-                                    if (schema)
-                                        *schema = sc;
-                                    new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(source, const_cast<SchemaType*>(sc), false, allocator_);
+                        if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) {
+                            const Ch* s = ref.GetFragString();
+                            len = ref.GetFragStringLength();
+                            if (len <= 1 || s[1] == '/') {
+                                // JSON pointer fragment, absolute in the remote schema
+                                const PointerType pointer(s, len, allocator_);
+                                if (pointer.IsValid()) {
+                                    // Get the subschema
+                                    if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) {
+                                        if (schema)
+                                            *schema = sc;
+                                        AddSchemaRefs(const_cast<SchemaType *>(sc));
+                                        return true;
+                                    }
+                                }
+                          } else {
+                            // Plain name fragment, not allowed
+                          }
+                        }
+                    }
+                }
+                else { // Local reference
+                    const Ch* s = ref.GetFragString();
+                    len = ref.GetFragStringLength();
+                    if (len <= 1 || s[1] == '/') {
+                        // JSON pointer fragment, relative to the resolved URI
+                        const PointerType relPointer(s, len, allocator_);
+                        if (relPointer.IsValid()) {
+                            // Get the subschema
+                            if (const ValueType *pv = relPointer.Get(*base)) {
+                                // Now get the absolute JSON pointer by adding relative to base
+                                PointerType pointer(basePointer);
+                                for (SizeType i = 0; i < relPointer.GetTokenCount(); i++)
+                                    pointer = pointer.Append(relPointer.GetTokens()[i], allocator_);
+                                //GenericStringBuffer<EncodingType> sb;
+                                //pointer.StringifyUriFragment(sb);
+                                if (pointer.IsValid() && !IsCyclicRef(pointer)) {
+                                    // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
+                                    // TODO: cache pointer <-> id mapping
+                                    size_t unresolvedTokenIndex;
+                                    scopeId = pointer.GetUri(document, docId_, &unresolvedTokenIndex, allocator_);
+                                    CreateSchema(schema, pointer, *pv, document, scopeId);
                                     return true;
                                 }
                             }
                         }
-                    }
-                }
-                else if (s[i] == '#') { // Local reference, defer resolution
-                    PointerType pointer(&s[i], len - i, allocator_);
-                    if (pointer.IsValid()) {
-                        if (const ValueType* nv = pointer.Get(document))
-                            if (HandleRefSchema(source, schema, *nv, document))
+                    } else {
+                        // Plain name fragment, relative to the resolved URI
+                        // See if the fragment matches an id in this document.
+                        // Search from the base we just established. Returns the subschema in the document and its absolute JSON pointer.
+                        PointerType pointer = PointerType();
+                        if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) {
+                            if (!IsCyclicRef(pointer)) {
+                                //GenericStringBuffer<EncodingType> sb;
+                                //pointer.StringifyUriFragment(sb);
+                                // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
+                                // TODO: cache pointer <-> id mapping
+                                size_t unresolvedTokenIndex;
+                                scopeId = pointer.GetUri(document, docId_, &unresolvedTokenIndex, allocator_);
+                                CreateSchema(schema, pointer, *pv, document, scopeId);
                                 return true;
-
-                        new (schemaRef_.template Push<SchemaRefEntry>()) SchemaRefEntry(source, pointer, schema, allocator_);
-                        return true;
+                            }
+                        }
                     }
                 }
             }
         }
+
+        // Invalid/Unknown $ref
+        if (schema)
+            *schema = typeless_;
+        AddSchemaRefs(typeless_);
+        return true;
+    }
+
+    //! Find the first subschema with a resolved 'id' that matches the specified URI.
+    // If full specified use all URI else ignore fragment.
+    // If found, return a pointer to the subschema and its JSON pointer.
+    // TODO cache pointer <-> id mapping
+    ValueType* FindId(const ValueType& doc, const UriType& finduri, PointerType& resptr, const UriType& baseuri, bool full, const PointerType& here = PointerType()) const {
+        SizeType i = 0;
+        ValueType* resval = 0;
+        UriType tempuri = UriType(finduri, allocator_);
+        UriType localuri = UriType(baseuri, allocator_);
+        if (doc.GetType() == kObjectType) {
+            // Establish the base URI of this object
+            typename ValueType::ConstMemberIterator m = doc.FindMember(SchemaType::GetIdString());
+            if (m != doc.MemberEnd() && m->value.GetType() == kStringType) {
+                localuri = UriType(m->value, allocator_).Resolve(baseuri, allocator_);
+            }
+            // See if it matches
+            if (localuri.Match(finduri, full)) {
+                resval = const_cast<ValueType *>(&doc);
+                resptr = here;
+                return resval;
+            }
+            // No match, continue looking
+            for (m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) {
+                if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) {
+                    resval = FindId(m->value, finduri, resptr, localuri, full, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_));
+                }
+                if (resval) break;
+            }
+        } else if (doc.GetType() == kArrayType) {
+            // Continue looking
+            for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) {
+                if (v->GetType() == kObjectType || v->GetType() == kArrayType) {
+                    resval = FindId(*v, finduri, resptr, localuri, full, here.Append(i, allocator_));
+                }
+                if (resval) break;
+                i++;
+            }
+        }
+        return resval;
+    }
+
+    // Added by PR #1393
+    void AddSchemaRefs(SchemaType* schema) {
+        while (!schemaRef_.Empty()) {
+            SchemaRefPtr *ref = schemaRef_.template Pop<SchemaRefPtr>(1);
+            SchemaEntry *entry = schemaMap_.template Push<SchemaEntry>();
+            new (entry) SchemaEntry(**ref, schema, false, allocator_);
+        }
+    }
+
+    // Added by PR #1393
+    bool IsCyclicRef(const PointerType& pointer) const {
+        for (const SchemaRefPtr* ref = schemaRef_.template Bottom<SchemaRefPtr>(); ref != schemaRef_.template End<SchemaRefPtr>(); ++ref)
+            if (pointer == **ref)
+                return true;
         return false;
     }
 
@@ -1811,8 +1957,9 @@
     const SchemaType* root_;                //!< Root schema.
     SchemaType* typeless_;
     internal::Stack<Allocator> schemaMap_;  // Stores created Pointer -> Schemas
-    internal::Stack<Allocator> schemaRef_;  // Stores Pointer from $ref and schema which holds the $ref
-    URIType uri_;
+    internal::Stack<Allocator> schemaRef_;  // Stores Pointer(s) from $ref(s) until resolved
+    SValue uri_;                            // Schema document URI
+    UriType docId_;
 };
 
 //! GenericSchemaDocument using Value type.
diff --git a/include/rapidjson/uri.h b/include/rapidjson/uri.h
new file mode 100644
index 0000000..7de7b80
--- /dev/null
+++ b/include/rapidjson/uri.h
@@ -0,0 +1,466 @@
+// Tencent is pleased to support the open source community by making RapidJSON available.
+//
+// (C) Copyright IBM Corporation 2021
+//
+// Licensed under the MIT License (the "License"); you may not use this file except
+// in compliance with the License. You may obtain a copy of the License at
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+
+#ifndef RAPIDJSON_URI_H_
+#define RAPIDJSON_URI_H_
+
+#include "internal/strfunc.h"
+
+#if defined(__clang__)
+RAPIDJSON_DIAG_PUSH
+RAPIDJSON_DIAG_OFF(c++98-compat)
+#elif defined(_MSC_VER)
+RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated
+#endif
+
+RAPIDJSON_NAMESPACE_BEGIN
+
+///////////////////////////////////////////////////////////////////////////////
+// GenericUri
+
+template <typename ValueType, typename Allocator=CrtAllocator>
+class GenericUri {
+public:
+    typedef typename ValueType::Ch Ch;
+#if RAPIDJSON_HAS_STDSTRING
+    typedef std::basic_string<Ch> String;
+#endif
+
+    //! Constructors
+    GenericUri(Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
+    }
+
+    GenericUri(const Ch* uri, SizeType len, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
+        Parse(uri, len);
+    }
+
+    GenericUri(const Ch* uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
+        Parse(uri, internal::StrLen<Ch>(uri));
+    }
+
+    // Use with specializations of GenericValue
+    template<typename T> GenericUri(const T& uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
+        const Ch* u = uri.template Get<const Ch*>(); // TypeHelper from document.h
+        Parse(u, internal::StrLen<Ch>(u));
+    }
+
+#if RAPIDJSON_HAS_STDSTRING
+    GenericUri(const String& uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
+        Parse(uri.c_str(), internal::StrLen<Ch>(uri.c_str()));
+    }
+#endif
+
+    //! Copy constructor
+    GenericUri(const GenericUri& rhs) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(), ownAllocator_() {
+        *this = rhs;
+    }
+
+    //! Copy constructor
+    GenericUri(const GenericUri& rhs, Allocator* allocator) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
+        *this = rhs;
+    }
+
+    //! Destructor.
+    ~GenericUri() {
+        Free();
+        RAPIDJSON_DELETE(ownAllocator_);
+    }
+
+    //! Assignment operator
+    GenericUri& operator=(const GenericUri& rhs) {
+        if (this != &rhs) {
+            // Do not delete ownAllocator
+            Free();
+            Allocate(rhs.GetStringLength());
+            auth_ = CopyPart(scheme_, rhs.scheme_, rhs.GetSchemeStringLength());
+            path_ = CopyPart(auth_, rhs.auth_, rhs.GetAuthStringLength());
+            query_ = CopyPart(path_, rhs.path_, rhs.GetPathStringLength());
+            frag_ = CopyPart(query_, rhs.query_, rhs.GetQueryStringLength());
+            base_ = CopyPart(frag_, rhs.frag_, rhs.GetFragStringLength());
+            uri_ = CopyPart(base_, rhs.base_, rhs.GetBaseStringLength());
+            CopyPart(uri_, rhs.uri_, rhs.GetStringLength());
+        }
+        return *this;
+    }
+
+    //! Getters
+    // Use with specializations of GenericValue
+    template<typename T> void Get(T& uri, Allocator& allocator) {
+        uri.template Set<const Ch*>(this->GetString(), allocator); // TypeHelper from document.h
+    }
+
+    const Ch* GetString() const { return uri_; }
+    SizeType GetStringLength() const { return uri_ == 0 ? 0 : internal::StrLen<Ch>(uri_); }
+    const Ch* GetBaseString() const { return base_; }
+    SizeType GetBaseStringLength() const { return base_ == 0 ? 0 : internal::StrLen<Ch>(base_); }
+    const Ch* GetSchemeString() const { return scheme_; }
+    SizeType GetSchemeStringLength() const { return scheme_ == 0 ? 0 : internal::StrLen<Ch>(scheme_); }
+    const Ch* GetAuthString() const { return auth_; }
+    SizeType GetAuthStringLength() const { return auth_ == 0 ? 0 : internal::StrLen<Ch>(auth_); }
+    const Ch* GetPathString() const { return path_; }
+    SizeType GetPathStringLength() const { return path_ == 0 ? 0 : internal::StrLen<Ch>(path_); }
+    const Ch* GetQueryString() const { return query_; }
+    SizeType GetQueryStringLength() const { return query_ == 0 ? 0 : internal::StrLen<Ch>(query_); }
+    const Ch* GetFragString() const { return frag_; }
+    SizeType GetFragStringLength() const { return frag_ == 0 ? 0 : internal::StrLen<Ch>(frag_); }
+
+#if RAPIDJSON_HAS_STDSTRING
+    static String Get(const GenericUri& uri) { return String(uri.GetString(), uri.GetStringLength()); }
+    static String GetBase(const GenericUri& uri) { return String(uri.GetBaseString(), uri.GetBaseStringLength()); }
+    static String GetScheme(const GenericUri& uri) { return String(uri.GetSchemeString(), uri.GetSchemeStringLength()); }
+    static String GetAuth(const GenericUri& uri) { return String(uri.GetAuthString(), uri.GetAuthStringLength()); }
+    static String GetPath(const GenericUri& uri) { return String(uri.GetPathString(), uri.GetPathStringLength()); }
+    static String GetQuery(const GenericUri& uri) { return String(uri.GetQueryString(), uri.GetQueryStringLength()); }
+    static String GetFrag(const GenericUri& uri) { return String(uri.GetFragString(), uri.GetFragStringLength()); }
+#endif
+
+    //! Equality operators
+    bool operator==(const GenericUri& rhs) const {
+        return Match(rhs, true);
+    }
+
+    bool operator!=(const GenericUri& rhs) const {
+        return !Match(rhs, true);
+    }
+
+    bool Match(const GenericUri& uri, bool full = true) const {
+        Ch* s1;
+        Ch* s2;
+        if (full) {
+            s1 = uri_;
+            s2 = uri.uri_;
+        } else {
+            s1 = base_;
+            s2 = uri.base_;
+        }
+        if (s1 == s2) return true;
+        if (s1 == 0 || s2 == 0) return false;
+        return internal::StrCmp<Ch>(s1, s2) == 0;
+    }
+
+    //! Resolve this URI against another (base) URI in accordance with URI resolution rules.
+    // See https://tools.ietf.org/html/rfc3986
+    // Use for resolving an id or $ref with an in-scope id.
+    // Returns a new GenericUri for the resolved URI.
+    GenericUri Resolve(const GenericUri& baseuri, Allocator* allocator = 0) {
+        GenericUri resuri;
+        resuri.allocator_ = allocator;
+        // Ensure enough space for combining paths
+        resuri.Allocate(GetStringLength() + baseuri.GetStringLength() + 1); // + 1 for joining slash
+
+        if (!(GetSchemeStringLength() == 0)) {
+            // Use all of this URI
+            resuri.auth_ = CopyPart(resuri.scheme_, scheme_, GetSchemeStringLength());
+            resuri.path_ = CopyPart(resuri.auth_, auth_, GetAuthStringLength());
+            resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength());
+            resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
+            resuri.RemoveDotSegments();
+        } else {
+            // Use the base scheme
+            resuri.auth_ = CopyPart(resuri.scheme_, baseuri.scheme_, baseuri.GetSchemeStringLength());
+            if (!(GetAuthStringLength() == 0)) {
+                // Use this auth, path, query
+                resuri.path_ = CopyPart(resuri.auth_, auth_, GetAuthStringLength());
+                resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength());
+                resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
+                resuri.RemoveDotSegments();
+            } else {
+                // Use the base auth
+                resuri.path_ = CopyPart(resuri.auth_, baseuri.auth_, baseuri.GetAuthStringLength());
+                if (GetPathStringLength() == 0) {
+                    // Use the base path
+                    resuri.query_ = CopyPart(resuri.path_, baseuri.path_, baseuri.GetPathStringLength());
+                    if (GetQueryStringLength() == 0) {
+                        // Use the base query
+                        resuri.frag_ = CopyPart(resuri.query_, baseuri.query_, baseuri.GetQueryStringLength());
+                    } else {
+                        // Use this query
+                        resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
+                    }
+                } else {
+                    if (path_[0] == '/') {
+                        // Absolute path - use all of this path
+                        resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength());
+                        resuri.RemoveDotSegments();
+                    } else {
+                        // Relative path - append this path to base path after base path's last slash
+                        size_t pos = 0;
+                        if (!(baseuri.GetAuthStringLength() == 0) && baseuri.GetPathStringLength() == 0) {
+                            resuri.path_[pos] = '/';
+                            pos++;
+                        }
+                        size_t lastslashpos = baseuri.GetPathStringLength();
+                        while (lastslashpos > 0) {
+                            if (baseuri.path_[lastslashpos - 1] == '/') break;
+                            lastslashpos--;
+                        }
+                        std::memcpy(&resuri.path_[pos], baseuri.path_, lastslashpos * sizeof(Ch));
+                        pos += lastslashpos;
+                        resuri.query_ = CopyPart(&resuri.path_[pos], path_, GetPathStringLength());
+                        resuri.RemoveDotSegments();
+                    }
+                    // Use this query
+                    resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
+                }
+            }
+        }
+        // Always use this frag
+        resuri.base_ = CopyPart(resuri.frag_, frag_, GetFragStringLength());
+
+        // Re-constitute base_ and uri_
+        resuri.SetBase();
+        resuri.uri_ = resuri.base_ + resuri.GetBaseStringLength() + 1;
+        resuri.SetUri();
+        return resuri;
+    }
+
+    //! Get the allocator of this GenericUri.
+    Allocator& GetAllocator() { return *allocator_; }
+
+private:
+    // Allocate memory for a URI
+    // Returns total amount allocated
+    std::size_t Allocate(std::size_t len) {
+        // Create own allocator if user did not supply.
+        if (!allocator_)
+            ownAllocator_ =  allocator_ = RAPIDJSON_NEW(Allocator)();
+
+        // Allocate one block containing each part of the URI (5) plus base plus full URI, all null terminated.
+        // Order: scheme, auth, path, query, frag, base, uri
+        size_t total = (3 * len + 7) * sizeof(Ch);
+        scheme_ = static_cast<Ch*>(allocator_->Malloc(total));
+        *scheme_ = '\0';
+        auth_ = scheme_ + 1;
+        *auth_ = '\0';
+        path_ = auth_ + 1;
+        *path_ = '\0';
+        query_ = path_ + 1;
+        *query_ = '\0';
+        frag_ = query_ + 1;
+        *frag_ = '\0';
+        base_ = frag_ + 1;
+        *base_ = '\0';
+        uri_ = base_ + 1;
+        *uri_ = '\0';
+        return total;
+    }
+
+    // Free memory for a URI
+    void Free() {
+        if (scheme_) {
+            Allocator::Free(scheme_);
+            scheme_ = 0;
+        }
+    }
+
+    // Parse a URI into constituent scheme, authority, path, query, & fragment parts
+    // Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per
+    // https://tools.ietf.org/html/rfc3986
+    void Parse(const Ch* uri, std::size_t len) {
+        std::size_t start = 0, pos1 = 0, pos2 = 0;
+        Allocate(len);
+
+        // Look for scheme ([^:/?#]+):)?
+        if (start < len) {
+            while (pos1 < len) {
+                if (uri[pos1] == ':') break;
+                pos1++;
+            }
+            if (pos1 != len) {
+                while (pos2 < len) {
+                    if (uri[pos2] == '/') break;
+                    if (uri[pos2] == '?') break;
+                    if (uri[pos2] == '#') break;
+                    pos2++;
+                }
+                if (pos1 < pos2) {
+                    pos1++;
+                    std::memcpy(scheme_, &uri[start], pos1 * sizeof(Ch));
+                    scheme_[pos1] = '\0';
+                    start = pos1;
+                }
+            }
+        }
+        // Look for auth (//([^/?#]*))?
+        auth_ = scheme_ + GetSchemeStringLength() + 1;
+        *auth_ = '\0';
+        if (start < len - 1 && uri[start] == '/' && uri[start + 1] == '/') {
+            pos2 = start + 2;
+            while (pos2 < len) {
+                if (uri[pos2] == '/') break;
+                if (uri[pos2] == '?') break;
+                if (uri[pos2] == '#') break;
+                pos2++;
+            }
+            std::memcpy(auth_, &uri[start], (pos2 - start) * sizeof(Ch));
+            auth_[pos2 - start] = '\0';
+            start = pos2;
+        }
+        // Look for path ([^?#]*)
+        path_ = auth_ + GetAuthStringLength() + 1;
+        *path_ = '\0';
+        if (start < len) {
+            pos2 = start;
+            while (pos2 < len) {
+                if (uri[pos2] == '?') break;
+                if (uri[pos2] == '#') break;
+                pos2++;
+            }
+            if (start != pos2) {
+                std::memcpy(path_, &uri[start], (pos2 - start) * sizeof(Ch));
+                path_[pos2 - start] = '\0';
+                if (path_[0] == '/')
+                    RemoveDotSegments();   // absolute path - normalize
+                start = pos2;
+            }
+        }
+        // Look for query (\?([^#]*))?
+        query_ = path_ + GetPathStringLength() + 1;
+        *query_ = '\0';
+        if (start < len && uri[start] == '?') {
+            pos2 = start + 1;
+            while (pos2 < len) {
+                if (uri[pos2] == '#') break;
+                pos2++;
+            }
+            if (start != pos2) {
+                std::memcpy(query_, &uri[start], (pos2 - start) * sizeof(Ch));
+                query_[pos2 - start] = '\0';
+                start = pos2;
+            }
+        }
+        // Look for fragment (#(.*))?
+        frag_ = query_ + GetQueryStringLength() + 1;
+        *frag_ = '\0';
+        if (start < len && uri[start] == '#') {
+            std::memcpy(frag_, &uri[start], (len - start) * sizeof(Ch));
+            frag_[len - start] = '\0';
+        }
+
+        // Re-constitute base_ and uri_
+        base_ = frag_ + GetFragStringLength() + 1;
+        SetBase();
+        uri_ = base_ + GetBaseStringLength() + 1;
+        SetUri();
+    }
+
+    // Reconstitute base
+    void SetBase() {
+        Ch* next = base_;
+        std::memcpy(next, scheme_, GetSchemeStringLength() * sizeof(Ch));
+        next+= GetSchemeStringLength();
+        std::memcpy(next, auth_, GetAuthStringLength() * sizeof(Ch));
+        next+= GetAuthStringLength();
+        std::memcpy(next, path_, GetPathStringLength() * sizeof(Ch));
+        next+= GetPathStringLength();
+        std::memcpy(next, query_, GetQueryStringLength() * sizeof(Ch));
+        next+= GetQueryStringLength();
+        *next = '\0';
+    }
+
+    // Reconstitute uri
+    void SetUri() {
+        Ch* next = uri_;
+        std::memcpy(next, base_, GetBaseStringLength() * sizeof(Ch));
+        next+= GetBaseStringLength();
+        std::memcpy(next, frag_, GetFragStringLength() * sizeof(Ch));
+        next+= GetFragStringLength();
+        *next = '\0';
+    }
+
+    // Copy a part from one GenericUri to another
+    // Return the pointer to the next part to be copied to
+    Ch* CopyPart(Ch* to, Ch* from, std::size_t len) {
+        RAPIDJSON_ASSERT(to != 0);
+        RAPIDJSON_ASSERT(from != 0);
+        std::memcpy(to, from, len * sizeof(Ch));
+        to[len] = '\0';
+        Ch* next = to + len + 1;
+        return next;
+    }
+
+    // Remove . and .. segments from the path_ member.
+    // https://tools.ietf.org/html/rfc3986
+    // This is done in place as we are only removing segments.
+    void RemoveDotSegments() {
+        std::size_t pathlen = GetPathStringLength();
+        std::size_t pathpos = 0;  // Position in path_
+        std::size_t newpos = 0;   // Position in new path_
+
+        // Loop through each segment in original path_
+        while (pathpos < pathlen) {
+            // Get next segment, bounded by '/' or end
+            size_t slashpos = 0;
+            while ((pathpos + slashpos) < pathlen) {
+                if (path_[pathpos + slashpos] == '/') break;
+                slashpos++;
+            }
+            // Check for .. and . segments
+            if (slashpos == 2 && path_[pathpos] == '.' && path_[pathpos + 1] == '.') {
+                // Backup a .. segment in the new path_
+                // We expect to find a previously added slash at the end or nothing
+                RAPIDJSON_ASSERT(newpos == 0 || path_[newpos - 1] == '/');
+                size_t lastslashpos = newpos;
+                // Make sure we don't go beyond the start segment
+                if (lastslashpos > 1) {
+                    // Find the next to last slash and back up to it
+                    lastslashpos--;
+                    while (lastslashpos > 0) {
+                        if (path_[lastslashpos - 1] == '/') break;
+                        lastslashpos--;
+                    }
+                    // Set the new path_ position
+                    newpos = lastslashpos;
+                }
+            } else if (slashpos == 1 && path_[pathpos] == '.') {
+                // Discard . segment, leaves new path_ unchanged
+            } else {
+                // Move any other kind of segment to the new path_
+                RAPIDJSON_ASSERT(newpos <= pathpos);
+                std::memmove(&path_[newpos], &path_[pathpos], slashpos * sizeof(Ch));
+                newpos += slashpos;
+                // Add slash if not at end
+                if ((pathpos + slashpos) < pathlen) {
+                    path_[newpos] = '/';
+                    newpos++;
+                }
+            }
+            // Move to next segment
+            pathpos += slashpos + 1;
+        }
+        path_[newpos] = '\0';
+    }
+
+    Ch* uri_;    // Everything
+    Ch* base_;   // Everything except fragment
+    Ch* scheme_; // Includes the :
+    Ch* auth_;   // Includes the //
+    Ch* path_;   // Absolute if starts with /
+    Ch* query_;  // Includes the ?
+    Ch* frag_;   // Includes the #
+
+    Allocator* allocator_;      //!< The current allocator. It is either user-supplied or equal to ownAllocator_.
+    Allocator* ownAllocator_;   //!< Allocator owned by this Uri.
+};
+
+//! GenericUri for Value (UTF-8, default allocator).
+typedef GenericUri<Value> Uri;
+
+RAPIDJSON_NAMESPACE_END
+
+#if defined(__clang__)
+RAPIDJSON_DIAG_POP
+#endif
+
+#endif // RAPIDJSON_URI_H_
diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt
index 0a8f2a3..565ed98 100644
--- a/test/unittest/CMakeLists.txt
+++ b/test/unittest/CMakeLists.txt
@@ -27,6 +27,7 @@
     stringbuffertest.cpp
     strtodtest.cpp
     unittest.cpp
+    uritest.cpp
     valuetest.cpp
     writertest.cpp)
 
diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp
index 50c678e..342086d 100644
--- a/test/unittest/pointertest.cpp
+++ b/test/unittest/pointertest.cpp
@@ -650,6 +650,52 @@
     }
 }
 
+static const char kJsonIds[] = "{\n"
+   "    \"id\": \"/root/\","
+   "    \"foo\":[\"bar\", \"baz\", {\"id\": \"inarray\", \"child\": 1}],\n"
+   "    \"int\" : 2,\n"
+   "    \"str\" : \"val\",\n"
+   "    \"obj\": {\"id\": \"inobj\", \"child\": 3},\n"
+   "    \"jbo\": {\"id\": true, \"child\": 4}\n"
+   "}";
+
+
+TEST(Pointer, GetUri) {
+    CrtAllocator allocator;
+    Document d;
+    d.Parse(kJsonIds);
+    Pointer::UriType doc("http://doc");
+    Pointer::UriType root("http://doc/root/");
+    Pointer::UriType empty = Pointer::UriType();
+
+    EXPECT_TRUE(Pointer("").GetUri(d, doc) == doc);
+    EXPECT_TRUE(Pointer("/foo").GetUri(d, doc) == root);
+    EXPECT_TRUE(Pointer("/foo/0").GetUri(d, doc) == root);
+    EXPECT_TRUE(Pointer("/foo/2").GetUri(d, doc) == root);
+    EXPECT_TRUE(Pointer("/foo/2/child").GetUri(d, doc) == Pointer::UriType("http://doc/root/inarray"));
+    EXPECT_TRUE(Pointer("/int").GetUri(d, doc) == root);
+    EXPECT_TRUE(Pointer("/str").GetUri(d, doc) == root);
+    EXPECT_TRUE(Pointer("/obj").GetUri(d, doc) == root);
+    EXPECT_TRUE(Pointer("/obj/child").GetUri(d, doc) == Pointer::UriType("http://doc/root/inobj"));
+    EXPECT_TRUE(Pointer("/jbo").GetUri(d, doc) == root);
+    EXPECT_TRUE(Pointer("/jbo/child").GetUri(d, doc) == root); // id not string
+
+    size_t unresolvedTokenIndex;
+    EXPECT_TRUE(Pointer("/abc").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // Out of boundary
+    EXPECT_EQ(0u, unresolvedTokenIndex);
+    EXPECT_TRUE(Pointer("/foo/3").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // Out of boundary
+    EXPECT_EQ(1u, unresolvedTokenIndex);
+    EXPECT_TRUE(Pointer("/foo/a").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // "/foo" is an array, cannot query by "a"
+    EXPECT_EQ(1u, unresolvedTokenIndex);
+    EXPECT_TRUE(Pointer("/foo/0/0").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // "/foo/0" is an string, cannot further query
+    EXPECT_EQ(2u, unresolvedTokenIndex);
+    EXPECT_TRUE(Pointer("/foo/0/a").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // "/foo/0" is an string, cannot further query
+    EXPECT_EQ(2u, unresolvedTokenIndex);
+
+    Pointer::Token tokens[] = { { "foo ...", 3, kPointerInvalidIndex } };
+    EXPECT_TRUE(Pointer(tokens, 1).GetUri(d, doc) == root);
+}
+
 TEST(Pointer, Get) {
     Document d;
     d.Parse(kJson);
@@ -666,7 +712,8 @@
     EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d));
     EXPECT_EQ(&d[" "], Pointer("/ ").Get(d));
     EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d));
-    EXPECT_TRUE(Pointer("/abc").Get(d) == 0);
+
+    EXPECT_TRUE(Pointer("/abc").Get(d) == 0);  // Out of boundary
     size_t unresolvedTokenIndex;
     EXPECT_TRUE(Pointer("/foo/2").Get(d, &unresolvedTokenIndex) == 0); // Out of boundary
     EXPECT_EQ(1u, unresolvedTokenIndex);
diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp
index f381b4e..1b25e2f 100644
--- a/test/unittest/schematest.cpp
+++ b/test/unittest/schematest.cpp
@@ -13,6 +13,7 @@
 // specific language governing permissions and limitations under the License.
 
 #define RAPIDJSON_SCHEMA_VERBOSE 0
+#define RAPIDJSON_HAS_STDSTRING 1
 
 #include "unittest.h"
 #include "rapidjson/schema.h"
@@ -1811,6 +1812,189 @@
         "}}");
 }
 
+TEST(SchemaValidator, SchemaPointer) {
+    Document sd;
+    sd.Parse(
+        "{"
+        "  \"swagger\": \"2.0\","
+        "  \"paths\": {"
+        "    \"/some/path\": {"
+        "      \"post\": {"
+        "        \"parameters\": ["
+        "          {"
+        "            \"in\": \"body\","
+        "            \"name\": \"body\","
+        "            \"schema\": {"
+        "              \"properties\": {"
+        "                \"a\": {"
+        "                  \"$ref\": \"#/definitions/Prop_a\""
+        "                },"
+        "                \"b\": {"
+        "                  \"type\": \"integer\""
+        "                }"
+        "              },"
+        "              \"type\": \"object\""
+        "            }"
+        "          }"
+        "        ],"
+        "        \"responses\": {"
+        "          \"200\": {"
+        "            \"schema\": {"
+        "              \"$ref\": \"#/definitions/Resp_200\""
+        "            }"
+        "          }"
+        "        }"
+        "      }"
+        "    }"
+        "  },"
+        "  \"definitions\": {"
+        "    \"Prop_a\": {"
+        "      \"properties\": {"
+        "        \"c\": {"
+        "          \"enum\": ["
+        "            \"C1\","
+        "            \"C2\","
+        "            \"C3\""
+        "          ],"
+        "          \"type\": \"string\""
+        "        },"
+        "        \"d\": {"
+        "          \"$ref\": \"#/definitions/Prop_d\""
+        "        },"
+        "        \"s\": {"
+        "          \"type\": \"string\""
+        "        }"
+        "      },"
+        "      \"required\": [\"c\"],"
+        "      \"type\": \"object\""
+        "    },"
+        "    \"Prop_d\": {"
+        "      \"properties\": {"
+        "        \"a\": {"
+        "          \"$ref\": \"#/definitions/Prop_a\""
+        "        },"
+        "        \"c\": {"
+        "          \"$ref\": \"#/definitions/Prop_a/properties/c\""
+        "        }"
+        "      },"
+        "      \"type\": \"object\""
+        "    },"
+        "    \"Resp_200\": {"
+        "      \"properties\": {"
+        "        \"e\": {"
+        "          \"type\": \"string\""
+        "        },"
+        "        \"f\": {"
+        "          \"type\": \"boolean\""
+        "        },"
+        "        \"cyclic_source\": {"
+        "          \"$ref\": \"#/definitions/Resp_200/properties/cyclic_target\""
+        "        },"
+        "        \"cyclic_target\": {"
+        "          \"$ref\": \"#/definitions/Resp_200/properties/cyclic_source\""
+        "        }"
+        "      },"
+        "      \"type\": \"object\""
+        "    }"
+        "  }"
+        "}");
+    SchemaDocument s1(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/parameters/0/schema"));
+    VALIDATE(s1,
+        "{"
+        "  \"a\": {"
+        "    \"c\": \"C1\","
+        "    \"d\": {"
+        "      \"a\": {"
+        "        \"c\": \"C2\""
+        "      },"
+        "      \"c\": \"C3\""
+        "    }"
+        "  },"
+        "  \"b\": 123"
+        "}",
+         true);
+    INVALIDATE(s1,
+        "{"
+        "  \"a\": {"
+        "    \"c\": \"C1\","
+        "    \"d\": {"
+        "      \"a\": {"
+        "        \"c\": \"C2\""
+        "      },"
+        "      \"c\": \"C3\""
+        "    }"
+        "  },"
+        "  \"b\": \"should be an int\""
+        "}",
+        "#/paths/~1some~1path/post/parameters/0/schema/properties/b", "type", "#/b",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\":\"#/b\","
+        "    \"schemaRef\":\"#/paths/~1some~1path/post/parameters/0/schema/properties/b\","
+        "    \"expected\": [\"integer\"], \"actual\":\"string\""
+        "}}");
+    INVALIDATE(s1,
+        "{"
+        "  \"a\": {"
+        "    \"c\": \"C1\","
+        "    \"d\": {"
+        "      \"a\": {"
+        "        \"c\": \"should be within enum\""
+        "      },"
+        "      \"c\": \"C3\""
+        "    }"
+        "  },"
+        "  \"b\": 123"
+        "}",
+        "#/definitions/Prop_a/properties/c", "enum", "#/a/d/a/c",
+        "{ \"enum\": {"
+        "    \"errorCode\": 19,"
+        "    \"instanceRef\":\"#/a/d/a/c\","
+        "    \"schemaRef\":\"#/definitions/Prop_a/properties/c\""
+        "}}");
+    INVALIDATE(s1,
+        "{"
+        "  \"a\": {"
+        "    \"c\": \"C1\","
+        "    \"d\": {"
+        "      \"a\": {"
+        "        \"s\": \"required 'c' is missing\""
+        "      }"
+        "    }"
+        "  },"
+        "  \"b\": 123"
+        "}",
+        "#/definitions/Prop_a", "required", "#/a/d/a",
+        "{ \"required\": {"
+        "    \"errorCode\": 15,"
+        "    \"missing\":[\"c\"],"
+        "    \"instanceRef\":\"#/a/d/a\","
+        "    \"schemaRef\":\"#/definitions/Prop_a\""
+        "}}");
+    SchemaDocument s2(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/responses/200/schema"));
+    VALIDATE(s2,
+        "{ \"e\": \"some string\", \"f\": false }",
+        true);
+    INVALIDATE(s2,
+        "{ \"e\": true, \"f\": false }",
+        "#/definitions/Resp_200/properties/e", "type", "#/e",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\":\"#/e\","
+        "    \"schemaRef\":\"#/definitions/Resp_200/properties/e\","
+        "    \"expected\": [\"string\"], \"actual\":\"boolean\""
+        "}}");
+    INVALIDATE(s2,
+        "{ \"e\": \"some string\", \"f\": 123 }",
+        "#/definitions/Resp_200/properties/f", "type", "#/f",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\":\"#/f\","
+        "    \"schemaRef\":\"#/definitions/Resp_200/properties/f\","
+        "    \"expected\": [\"boolean\"], \"actual\":\"integer\""
+        "}}");
+}
+
 template <typename Allocator>
 static char* ReadFile(const char* filename, Allocator& allocator) {
     const char *paths[] = {
@@ -1952,7 +2136,7 @@
 
     virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) {
         for (size_t i = 0; i < kCount; i++)
-            if (typename SchemaDocumentType::URIType(uri, length) == sd_[i]->GetURI())
+            if (typename SchemaDocumentType::SValue(uri, length) == sd_[i]->GetURI())
                 return sd_[i];
         return 0;
     }
@@ -2032,7 +2216,7 @@
             ADD_FAILURE();
         }
         else {
-            //printf("json test suite file %s parsed ok\n", filename);
+            //printf("\njson test suite file %s parsed ok\n", filename);
             GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator);
             d.Parse(json);
             if (d.HasParseError()) {
@@ -2042,12 +2226,14 @@
             else {
                 for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) {
                     {
+                        const char* description1 = (*schemaItr)["description"].GetString();
+                        //printf("\ncompiling schema for json test %s \n", description1);
                         SchemaDocumentType schema((*schemaItr)["schema"], filenames[i], static_cast<SizeType>(strlen(filenames[i])), &provider, &schemaAllocator);
                         GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator);
-                        const char* description1 = (*schemaItr)["description"].GetString();
                         const Value& tests = (*schemaItr)["tests"];
                         for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) {
                             const char* description2 = (*testItr)["description"].GetString();
+                            //printf("running json test %s \n", description2);
                             if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) {
                                 const Value& data = (*testItr)["data"];
                                 bool expected = (*testItr)["valid"].GetBool();
@@ -2075,8 +2261,8 @@
         jsonAllocator.Clear();
     }
     printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount);
-//    if (passCount != testCount)
-//        ADD_FAILURE();
+    if (passCount != testCount)
+        ADD_FAILURE();
 }
 
 TEST(SchemaValidatingReader, Simple) {
@@ -2114,7 +2300,7 @@
     Document e;
     e.Parse(
         "{ \"maxLength\": {"
-"            \"errorCode\": 6,"
+        "     \"errorCode\": 6,"
         "    \"instanceRef\": \"#\", \"schemaRef\": \"#\","
         "    \"expected\": 3, \"actual\": \"ABCD\""
         "}}");
@@ -2244,6 +2430,185 @@
         kValidateDefaultFlags, SchemaValidatorType, PointerType);
 }
 
+// Merge with id where $ref is full URI
+TEST(SchemaValidator, Ref_remote_change_resolution_scope_uri) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
+    Document sd;
+    sd.Parse("{\"id\": \"http://ignore/blah#/ref\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"http://localhost:1234/subSchemas.json#/integer\"}}}");
+    SchemaDocumentType s(sd, 0, 0, &provider);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt\","
+        "    \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// Merge with id where $ref is a relative path
+TEST(SchemaValidator, Ref_remote_change_resolution_scope_relative_path) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
+    Document sd;
+    sd.Parse("{\"id\": \"http://localhost:1234/\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"subSchemas.json#/integer\"}}}");
+    SchemaDocumentType s(sd, 0, 0, &provider);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt\","
+        "    \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// Merge with id where $ref is an absolute path
+TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
+    Document sd;
+    sd.Parse("{\"id\": \"http://localhost:1234/xxxx\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}");
+    SchemaDocumentType s(sd, 0, 0, &provider);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt\","
+        "    \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// Merge with id where $ref is an absolute path, and the document has a base URI
+TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path_document) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
+    Document sd;
+    sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}");
+    SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt\","
+        "    \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// $ref is a non-JSON pointer fragment and there a matching id
+TEST(SchemaValidator, Ref_internal_id_1) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    Document sd;
+    sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}");
+    SchemaDocumentType s(sd);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt1\","
+        "    \"schemaRef\": \"#/properties/myInt2\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// $ref is a non-JSON pointer fragment and there are two matching ids so we take the first
+TEST(SchemaValidator, Ref_internal_id_2) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    Document sd;
+    sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myId\"}}}");
+    SchemaDocumentType s(sd);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt1\","
+        "    \"schemaRef\": \"#/properties/myInt2\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// $ref is a non-JSON pointer fragment and there is a matching id within array
+TEST(SchemaValidator, Ref_internal_id_in_array) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    Document sd;
+    sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"string\", \"id\": \"#myStrId\"}, {\"type\": \"integer\", \"id\": \"#myId\"}]}}}");
+    SchemaDocumentType s(sd);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2/anyOf/1", "type", "/myInt1",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt1\","
+        "    \"schemaRef\": \"#/properties/myInt2/anyOf/1\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// $ref is a non-JSON pointer fragment and there is a matching id, and the schema is embedded in the document
+TEST(SchemaValidator, Ref_internal_id_and_schema_pointer) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    Document sd;
+    sd.Parse("{ \"schema\": {\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"integer\", \"id\": \"#myId\"}]}}}}");
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    SchemaDocumentType s(sd, 0, 0, 0, 0, PointerType("/schema"));
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    INVALIDATE_(s, "{\"myInt1\": null}", "/schema/properties/myInt2/anyOf/0", "type", "/myInt1",
+        "{ \"type\": {"
+        "    \"errorCode\": 20,"
+        "    \"instanceRef\": \"#/myInt1\","
+        "    \"schemaRef\": \"#/schema/properties/myInt2/anyOf/0\","
+        "    \"expected\": [\"integer\"], \"actual\": \"null\""
+        "}}",
+        kValidateDefaultFlags, SchemaValidatorType, PointerType);
+}
+
+// Test that $refs are correctly resolved when intermediate multiple ids are present
+// Includes $ref to a part of the document with a different in-scope id, which also contains $ref..
+TEST(SchemaValidator, Ref_internal_multiple_ids) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    //RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
+    CrtAllocator allocator;
+    char* schema = ReadFile("unittestschema/idandref.json", allocator);
+    Document sd;
+    sd.Parse(schema);
+    ASSERT_FALSE(sd.HasParseError());
+    SchemaDocumentType s(sd, "http://xyz", 10/*, &provider*/);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"PA1\": \"s\", \"PA2\": \"t\", \"PA3\": \"r\", \"PX1\": 1, \"PX2Y\": 2, \"PX3Z\": 3, \"PX4\": 4, \"PX5\": 5, \"PX6\": 6, \"PX7W\": 7, \"PX8N\": { \"NX\": 8}}", "#", "errors", "#",
+        "{ \"type\": ["
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PA1\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PA2\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PA3\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX1\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX2Y\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX3Z\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX4\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX5\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX6\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX7W\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX8N/NX\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}"
+        "]}",
+        kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidatorType, PointerType);
+    CrtAllocator::Free(schema);
+}
+
 TEST(SchemaValidator, Ref_remote_issue1210) {
     class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider {
         SchemaDocument** collection;
@@ -2260,7 +2625,7 @@
           SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { }
           virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) {
             int i = 0;
-            while (collection[i] && SchemaDocument::URIType(uri, length) != collection[i]->GetURI()) ++i;
+            while (collection[i] && SchemaDocument::SValue(uri, length) != collection[i]->GetURI()) ++i;
             return collection[i];
           }
     };
diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp
new file mode 100644
index 0000000..5506aa1
--- /dev/null
+++ b/test/unittest/uritest.cpp
@@ -0,0 +1,718 @@
+// Tencent is pleased to support the open source community by making RapidJSON available.
+//
+// (C) Copyright IBM Corporation 2021
+//
+// Licensed under the MIT License (the "License"); you may not use this file except
+// in compliance with the License. You may obtain a copy of the License at
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless required by applicable law or agreed to in writing, software distributed
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+
+#define RAPIDJSON_SCHEMA_VERBOSE 0
+#define RAPIDJSON_HAS_STDSTRING 1
+
+#include "unittest.h"
+#include "rapidjson/document.h"
+#include "rapidjson/uri.h"
+
+#ifdef __clang__
+RAPIDJSON_DIAG_PUSH
+RAPIDJSON_DIAG_OFF(variadic-macros)
+#elif defined(_MSC_VER)
+RAPIDJSON_DIAG_PUSH
+RAPIDJSON_DIAG_OFF(4822) // local class member function does not have a body
+#endif
+
+using namespace rapidjson;
+
+TEST(Uri, DefaultConstructor) {
+    typedef GenericUri<Value> UriType;
+    UriType u;
+    EXPECT_TRUE(u.GetSchemeString() == 0);
+    EXPECT_TRUE(u.GetAuthString() == 0);
+    EXPECT_TRUE(u.GetPathString() == 0);
+    EXPECT_TRUE(u.GetBaseString() == 0);
+    EXPECT_TRUE(u.GetQueryString() == 0);
+    EXPECT_TRUE(u.GetFragString() == 0);
+    EXPECT_TRUE(u.GetString() == 0);
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(u.GetPathStringLength() == 0);
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+    EXPECT_TRUE(u.GetStringLength() == 0);
+}
+
+
+TEST(Uri, Parse) {
+    typedef GenericUri<Value, MemoryPoolAllocator<> > UriType;
+    MemoryPoolAllocator<CrtAllocator> allocator;
+    Value v;
+    Value w;
+
+    v.SetString("http://auth/path/xxx?query#frag", allocator);
+    UriType u = UriType(v, &allocator);
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0);
+    EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), "/path/xxx") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth/path/xxx?query") == 0);
+    EXPECT_TRUE(StrCmp(u.GetQueryString(), "?query") == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag") == 0);
+    u.Get(w, allocator);
+    EXPECT_TRUE(*w.GetString() == *v.GetString());
+
+#if RAPIDJSON_HAS_STDSTRING
+    typedef std::basic_string<Value::Ch> String;
+    String str = "http://auth/path/xxx?query#frag";
+    const UriType uri = UriType(str);
+    EXPECT_TRUE(UriType::GetScheme(uri) == "http:");
+    EXPECT_TRUE(UriType::GetAuth(uri) == "//auth");
+    EXPECT_TRUE(UriType::GetPath(uri) == "/path/xxx");
+    EXPECT_TRUE(UriType::GetBase(uri) == "http://auth/path/xxx?query");
+    EXPECT_TRUE(UriType::GetQuery(uri) == "?query");
+    EXPECT_TRUE(UriType::GetFrag(uri) == "#frag");
+    EXPECT_TRUE(UriType::Get(uri) == str);
+#endif
+
+    v.SetString("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f", allocator);
+    u = UriType(v);
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), "urn:") == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+    u.Get(w, allocator);
+    EXPECT_TRUE(*w.GetString() == *v.GetString());
+
+    v.SetString("", allocator);
+    u = UriType(v);
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(u.GetPathStringLength() == 0);
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    v.SetString("http://auth/", allocator);
+    u = UriType(v);
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0);
+    EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), "/") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth/") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    u = UriType("/path/sub");
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), "/path/sub") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "/path/sub") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    // absolute path gets normalized
+    u = UriType("/path/../sub/");
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), "/sub/") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "/sub/") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    // relative path does not
+    u = UriType("path/../sub");
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), "path/../sub") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "path/../sub") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    u = UriType("http://auth#frag/stuff");
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0);
+    EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0);
+    EXPECT_TRUE(u.GetPathStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0);
+    EXPECT_TRUE(StrCmp(u.GetString(), "http://auth#frag/stuff") == 0);
+
+    const Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'};
+    SizeType len = internal::StrLen<Value::Ch>(c);
+    u = UriType(c, len);
+    EXPECT_TRUE(StrCmp(u.GetString(), "#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetStringLength() == len);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "") == 0);
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == len);
+
+    u = UriType(c);
+    EXPECT_TRUE(StrCmp(u.GetString(), "#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetStringLength() == len);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), "") == 0);
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == len);
+
+    // Incomplete auth treated as path
+    str = "http:/";
+    const UriType u2 = UriType(str);
+    EXPECT_TRUE(StrCmp(u2.GetSchemeString(), "http:") == 0);
+    EXPECT_TRUE(u2.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u2.GetPathString(), "/") == 0);
+    EXPECT_TRUE(StrCmp(u2.GetBaseString(), "http:/") == 0);
+}
+
+TEST(Uri, Parse_UTF16) {
+    typedef GenericValue<UTF16<> > Value16;
+    typedef GenericUri<Value16, MemoryPoolAllocator<> > UriType;
+    MemoryPoolAllocator<CrtAllocator> allocator;
+    Value16 v;
+    Value16 w;
+
+    v.SetString(L"http://auth/path/xxx?query#frag", allocator);
+    UriType u = UriType(v, &allocator);
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0);
+    EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), L"/path/xxx") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth/path/xxx?query") == 0);
+    EXPECT_TRUE(StrCmp(u.GetQueryString(), L"?query") == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag") == 0);
+    u.Get(w, allocator);
+    EXPECT_TRUE(*w.GetString() == *v.GetString());
+
+#if RAPIDJSON_HAS_STDSTRING
+    typedef std::basic_string<Value16::Ch> String;
+    String str = L"http://auth/path/xxx?query#frag";
+    const UriType uri = UriType(str);
+    EXPECT_TRUE(UriType::GetScheme(uri) == L"http:");
+    EXPECT_TRUE(UriType::GetAuth(uri) == L"//auth");
+    EXPECT_TRUE(UriType::GetPath(uri) == L"/path/xxx");
+    EXPECT_TRUE(UriType::GetBase(uri) == L"http://auth/path/xxx?query");
+    EXPECT_TRUE(UriType::GetQuery(uri) == L"?query");
+    EXPECT_TRUE(UriType::GetFrag(uri) == L"#frag");
+    EXPECT_TRUE(UriType::Get(uri) == str);
+#endif
+
+    v.SetString(L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f", allocator);
+    u = UriType(v);
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"urn:") == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), L"uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+    u.Get(w, allocator);
+    EXPECT_TRUE(*w.GetString() == *v.GetString());
+
+    v.SetString(L"", allocator);
+    u = UriType(v);
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(u.GetPathStringLength() == 0);
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    v.SetString(L"http://auth/", allocator);
+    u = UriType(v);
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0);
+    EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), L"/") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth/") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    u = UriType(L"/path/sub");
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), L"/path/sub") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"/path/sub") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    // absolute path gets normalized
+    u = UriType(L"/path/../sub/");
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), L"/sub/") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"/sub/") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    // relative path does not
+    u = UriType(L"path/../sub");
+    EXPECT_TRUE(u.GetSchemeStringLength() == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), L"path/../sub") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"path/../sub") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == 0);
+
+    u = UriType(L"http://auth#frag/stuff");
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0);
+    EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0);
+    EXPECT_TRUE(u.GetPathStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth") == 0);
+    EXPECT_TRUE(u.GetQueryStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0);
+    EXPECT_TRUE(StrCmp(u.GetString(), L"http://auth#frag/stuff") == 0);
+
+    const Value16::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'};
+    SizeType len = internal::StrLen<Value16::Ch>(c);
+    u = UriType(c, len);
+    EXPECT_TRUE(StrCmp(u.GetString(), L"#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetStringLength() == len);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"") == 0);
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == len);
+
+    u = UriType(c);
+    EXPECT_TRUE(StrCmp(u.GetString(), L"#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetStringLength() == len);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"") == 0);
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0);
+    EXPECT_TRUE(u.GetFragStringLength() == len);
+
+    // Incomplete auth treated as path
+    u = UriType(L"http:/");
+    EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0);
+    EXPECT_TRUE(u.GetAuthStringLength() == 0);
+    EXPECT_TRUE(StrCmp(u.GetPathString(), L"/") == 0);
+    EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http:/") == 0);
+}
+
+TEST(Uri, CopyConstructor) {
+    typedef GenericUri<Value> UriType;
+    CrtAllocator allocator;
+
+    UriType u("http://auth/path/xxx?query#frag", &allocator);
+    UriType u2(u);
+    EXPECT_TRUE(u == u2);
+    EXPECT_NE(&u.GetAllocator(), &u2.GetAllocator());
+}
+
+TEST(Uri, Assignment) {
+    typedef GenericUri<Value> UriType;
+    CrtAllocator allocator;
+
+    UriType u("http://auth/path/xxx?query#frag", &allocator);
+    UriType u2;
+    u2 = u;
+    EXPECT_TRUE(u == u2);
+    EXPECT_NE(&u.GetAllocator(), &u2.GetAllocator());
+}
+
+TEST(Uri, Resolve) {
+    typedef GenericUri<Value> UriType;
+    CrtAllocator allocator;
+
+    // ref is full uri
+    UriType base = UriType("http://auth/path/#frag");
+    UriType ref = UriType("http://newauth/newpath#newfrag");
+    UriType res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0);
+
+    base = UriType("/path/#frag", &allocator);
+    ref = UriType("http://newauth/newpath#newfrag", &allocator);
+    res = ref.Resolve(base, &allocator);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0);
+
+    // ref is alternate uri
+    base = UriType("http://auth/path/#frag");
+    ref = UriType("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
+
+    // ref is absolute path
+    base = UriType("http://auth/path/#");
+    ref = UriType("/newpath#newfrag");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/newpath#newfrag") == 0);
+
+    // ref is relative path
+    base = UriType("http://auth/path/file.json#frag");
+    ref = UriType("newfile.json#");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/path/newfile.json#") == 0);
+
+    base = UriType("http://auth/path/file.json#frag/stuff");
+    ref = UriType("newfile.json#newfrag/newstuff");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/path/newfile.json#newfrag/newstuff") == 0);
+
+    base = UriType("file.json", &allocator);
+    ref = UriType("newfile.json", &base.GetAllocator());
+    res = ref.Resolve(base, &ref.GetAllocator());
+    EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
+
+    base = UriType("file.json", &allocator);
+    ref = UriType("./newfile.json", &allocator);
+    res = ref.Resolve(base, &allocator);
+    EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
+
+    base = UriType("file.json");
+    ref = UriType("parent/../newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
+
+    base = UriType("file.json");
+    ref = UriType("parent/./newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "parent/newfile.json") == 0);
+
+    base = UriType("file.json");
+    ref = UriType("../../parent/.././newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
+
+    // This adds a joining slash so resolved length is base length + ref length + 1
+    base = UriType("http://auth");
+    ref = UriType("newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/newfile.json") == 0);
+
+    // ref is fragment
+    base = UriType("#frag/stuff");
+    ref = UriType("#newfrag/newstuff");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "#newfrag/newstuff") == 0);
+
+    // test ref fragment always wins
+    base = UriType("/path#frag");
+    ref = UriType("");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "/path") == 0);
+
+    // Examples from RFC3896
+    base = UriType("http://a/b/c/d;p?q");
+    ref = UriType("g:h");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "g:h") == 0);
+    ref = UriType("g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g") == 0);
+    ref = UriType("./g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g") == 0);
+    ref = UriType("g/");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g/") == 0);
+    ref = UriType("/g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
+    ref = UriType("//g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://g") == 0);
+    ref = UriType("?y");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?y") == 0);
+    ref = UriType("g?y");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g?y") == 0);
+    ref = UriType("#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?q#s") == 0);
+    ref = UriType("g#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g#s") == 0);
+    ref = UriType("g?y#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g?y#s") == 0);
+    ref = UriType(";x");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/;x") == 0);
+    ref = UriType("g;x");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g;x") == 0);
+    ref = UriType("g;x?y#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g;x?y#s") == 0);
+    ref = UriType("");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?q") == 0);
+    ref = UriType(".");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/") == 0);
+    ref = UriType("./");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/") == 0);
+    ref = UriType("..");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/") == 0);
+    ref = UriType("../");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/") == 0);
+    ref = UriType("../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/g") == 0);
+    ref = UriType("../..");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/") == 0);
+    ref = UriType("../../");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/") == 0);
+    ref = UriType("../../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
+    ref = UriType("../../../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
+    ref = UriType("../../../../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
+    ref = UriType("/./g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
+    ref = UriType("/../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
+    ref = UriType("g.");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g.") == 0);
+    ref = UriType(".g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/.g") == 0);
+    ref = UriType("g..");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g..") == 0);
+    ref = UriType("..g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/..g") == 0);
+    ref = UriType("g#s/../x");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g#s/../x") == 0);
+}
+
+TEST(Uri, Resolve_UTF16) {
+    typedef GenericValue<UTF16<> > Value16;
+    typedef GenericUri<Value16> UriType;
+    CrtAllocator allocator;
+
+    // ref is full uri
+    UriType base = UriType(L"http://auth/path/#frag");
+    UriType ref = UriType(L"http://newauth/newpath#newfrag");
+    UriType res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://newauth/newpath#newfrag") == 0);
+
+    base = UriType(L"/path/#frag");
+    ref = UriType(L"http://newauth/newpath#newfrag");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://newauth/newpath#newfrag") == 0);
+
+    // ref is alternate uri
+    base = UriType(L"http://auth/path/#frag");
+    ref = UriType(L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
+
+    // ref is absolute path
+    base = UriType(L"http://auth/path/#");
+    ref = UriType(L"/newpath#newfrag");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/newpath#newfrag") == 0);
+
+    // ref is relative path
+    base = UriType(L"http://auth/path/file.json#frag");
+    ref = UriType(L"newfile.json#");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/path/newfile.json#") == 0);
+
+    base = UriType(L"http://auth/path/file.json#frag/stuff");
+    ref = UriType(L"newfile.json#newfrag/newstuff");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/path/newfile.json#newfrag/newstuff") == 0);
+
+    base = UriType(L"file.json", &allocator);
+    ref = UriType(L"newfile.json", &base.GetAllocator());
+    res = ref.Resolve(base, &ref.GetAllocator());
+    EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
+
+    base = UriType(L"file.json", &allocator);
+    ref = UriType(L"./newfile.json", &allocator);
+    res = ref.Resolve(base, &allocator);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
+
+    base = UriType(L"file.json");
+    ref = UriType(L"parent/../newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
+
+    base = UriType(L"file.json");
+    ref = UriType(L"parent/./newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"parent/newfile.json") == 0);
+
+    base = UriType(L"file.json");
+    ref = UriType(L"../../parent/.././newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
+
+    // This adds a joining slash so resolved length is base length + ref length + 1
+    base = UriType(L"http://auth");
+    ref = UriType(L"newfile.json");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/newfile.json") == 0);
+
+    // ref is fragment
+    base = UriType(L"#frag/stuff");
+    ref = UriType(L"#newfrag/newstuff");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"#newfrag/newstuff") == 0);
+
+    // test ref fragment always wins
+    base = UriType(L"/path#frag");
+    ref = UriType(L"");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"/path") == 0);
+
+    // Examples from RFC3896
+    base = UriType(L"http://a/b/c/d;p?q");
+    ref = UriType(L"g:h");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"g:h") == 0);
+    ref = UriType(L"g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g") == 0);
+    ref = UriType(L"./g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g") == 0);
+    ref = UriType(L"g/");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g/") == 0);
+    ref = UriType(L"/g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
+    ref = UriType(L"//g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://g") == 0);
+    ref = UriType(L"?y");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?y") == 0);
+    ref = UriType(L"g?y");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g?y") == 0);
+    ref = UriType(L"#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?q#s") == 0);
+    ref = UriType(L"g#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g#s") == 0);
+    ref = UriType(L"g?y#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g?y#s") == 0);
+    ref = UriType(L";x");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/;x") == 0);
+    ref = UriType(L"g;x");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g;x") == 0);
+    ref = UriType(L"g;x?y#s");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g;x?y#s") == 0);
+    ref = UriType(L"");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?q") == 0);
+    ref = UriType(L".");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/") == 0);
+    ref = UriType(L"./");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/") == 0);
+    ref = UriType(L"..");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/") == 0);
+    ref = UriType(L"../");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/") == 0);
+    ref = UriType(L"../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/g") == 0);
+    ref = UriType(L"../..");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/") == 0);
+    ref = UriType(L"../../");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/") == 0);
+    ref = UriType(L"../../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
+    ref = UriType(L"../../../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
+    ref = UriType(L"../../../../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
+    ref = UriType(L"/./g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
+    ref = UriType(L"/../g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
+    ref = UriType(L"g.");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g.") == 0);
+    ref = UriType(L".g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/.g") == 0);
+    ref = UriType(L"g..");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g..") == 0);
+    ref = UriType(L"..g");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/..g") == 0);
+    ref = UriType(L"g#s/../x");
+    res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g#s/../x") == 0);
+}
+
+TEST(Uri, Equals) {
+    typedef GenericUri<Value> UriType;
+
+    UriType a = UriType("http://a/a#a");
+    UriType b = UriType("http://a/a#b");
+    UriType c = a;
+
+    EXPECT_TRUE(a == a);
+    EXPECT_TRUE(a == c);
+    EXPECT_TRUE(a != b);
+}
+
+TEST(Uri, Match) {
+    typedef GenericUri<Value> UriType;
+
+    UriType a = UriType("http://a/a#a");
+    UriType b = UriType("http://a/a#b");
+    UriType c = a;
+    UriType d;
+
+    EXPECT_TRUE(a.Match(a));
+    EXPECT_TRUE(a.Match(c));
+    EXPECT_FALSE(a.Match(b));
+    EXPECT_FALSE(a.Match(b, true));
+    EXPECT_TRUE(a.Match(b, false));  // Base Uri same
+    EXPECT_FALSE(a.Match(d));
+    EXPECT_FALSE(d.Match(a));
+}
+
+TEST(Uri, Issue1899) {
+    typedef GenericUri<Value, MemoryPoolAllocator<> > UriType;
+
+    UriType base = UriType("http://auth/path/#frag");
+    UriType ref = UriType("http://newauth/newpath#newfrag");
+    UriType res = ref.Resolve(base);
+    EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0);
+}
+
+#if defined(_MSC_VER) || defined(__clang__)
+RAPIDJSON_DIAG_POP
+#endif