Repository: ogen-go/ogen
Pull request #436 (2022-06-29 04:11:14)

fix(openapi): add more path validation according to spec


Patch:
diff --git a/openapi/parser/parser.go b/openapi/parser/parser.go
index 8eeb26f6..e10fbad6 100644
--- a/openapi/parser/parser.go
+++ b/openapi/parser/parser.go
@@ -148,7 +148,7 @@ func (p *parser) parseOp(path, httpMethod string, spec ogen.Operation, itemParam
 
 	op.Path, err = parsePath(path, op.Parameters)
 	if err != nil {
-		return nil, errors.Wrap(err, "parse path")
+		return nil, errors.Wrapf(err, "parse path %q", path)
 	}
 
 	if spec.RequestBody != nil {
diff --git a/openapi/parser/path_parser.go b/openapi/parser/path_parser.go
index f4732dad..b83504f0 100644
--- a/openapi/parser/path_parser.go
+++ b/openapi/parser/path_parser.go
@@ -1,7 +1,11 @@
 package parser
 
 import (
+	"net/url"
+	"strings"
+
 	"github.com/go-faster/errors"
+
 	"github.com/ogen-go/ogen/openapi"
 )
 
@@ -27,7 +31,23 @@ func (p *pathParser) Parse() ([]openapi.PathPart, error) {
 }
 
 func (p *pathParser) parse() error {
-	for _, r := range p.path {
+	// Validate and unescape path.
+	u, err := url.Parse(p.path)
+	if err != nil {
+		return err
+	}
+	switch {
+	case u.IsAbs() || u.Host != "" || u.User != nil:
+		return errors.New("path MUST be relative")
+	case u.Path == "" || !strings.HasPrefix(u.Path, "/"):
+		return errors.New("path MUST begin with a forward slash")
+	case u.RawQuery != "":
+		return errors.New("path MUST NOT contain a query string")
+	case u.Fragment != "" || u.RawFragment != "":
+		return errors.New("path MUST NOT contain a fragment")
+	}
+
+	for _, r := range u.Path {
 		switch r {
 		case '/':
 			if p.param {


Test patch:
diff --git a/_testdata/positive/autorest/xml-service.json b/_testdata/positive/autorest/xml-service.json
deleted file mode 100644
index 8e6b6ff6..00000000
--- a/_testdata/positive/autorest/xml-service.json
+++ /dev/null
@@ -1,1443 +0,0 @@
-{
-  "openapi": "3.0.0",
-  "servers": [
-    {
-      "url": "http://localhost:3000/"
-    }
-  ],
-  "info": {
-    "version": "1.0.0",
-    "title": "AutoRest Swagger BAT XML Service",
-    "description": "Test Infrastructure for AutoRest Swagger BAT"
-  },
-  "paths": {
-    "/xml/complex-type-ref-no-meta": {
-      "get": {
-        "operationId": "xml_getComplexTypeRefNoMeta",
-        "description": "Get a complex type that has a ref to a complex type with no XML node",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The empty list.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/RootWithRefAndNoMeta"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putComplexTypeRefNoMeta",
-        "description": "Puts a complex type that has a ref to a complex type with no XML node",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "model",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/RootWithRefAndNoMeta"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/complex-type-ref-with-meta": {
-      "get": {
-        "operationId": "xml_getComplexTypeRefWithMeta",
-        "description": "Get a complex type that has a ref to a complex type with XML node",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The empty list.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/RootWithRefAndMeta"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putComplexTypeRefWithMeta",
-        "description": "Puts a complex type that has a ref to a complex type with XML node",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "model",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/RootWithRefAndMeta"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/simple": {
-      "get": {
-        "operationId": "xml_getSimple",
-        "description": "Get a simple XML document",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The simple XML document",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/Slideshow"
-                }
-              }
-            }
-          },
-          "default": {
-            "description": "Unexpected error",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/Error"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putSimple",
-        "description": "Put a simple XML document",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success"
-          },
-          "default": {
-            "description": "Unexpected error",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/Error"
-                }
-              }
-            }
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "slideshow",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/Slideshow"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/wrapped-lists": {
-      "get": {
-        "operationId": "xml_getWrappedLists",
-        "description": "Get an XML document with multiple wrapped lists",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The XML document with multiple wrapped lists",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/AppleBarrel"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putWrappedLists",
-        "description": "Put an XML document with multiple wrapped lists",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success"
-          },
-          "default": {
-            "description": "Unexpected error",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/Error"
-                }
-              }
-            }
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "wrappedLists",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/AppleBarrel"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/headers": {
-      "get": {
-        "operationId": "xml_getHeaders",
-        "description": "Get strongly-typed response headers.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The response header.",
-            "headers": {
-              "Custom-Header": {
-                "description": "A custom response header.",
-                "schema": {
-                  "type": "string"
-                }
-              }
-            }
-          }
-        }
-      }
-    },
-    "/xml/empty-list": {
-      "get": {
-        "operationId": "xml_getEmptyList",
-        "description": "Get an empty list.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The empty list.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/Slideshow"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putEmptyList",
-        "description": "Puts an empty list.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "slideshow",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/Slideshow"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/empty-wrapped-lists": {
-      "get": {
-        "operationId": "xml_getEmptyWrappedLists",
-        "description": "Gets some empty wrapped lists.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The empty lists.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/AppleBarrel"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putEmptyWrappedLists",
-        "description": "Puts some empty wrapped lists.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "appleBarrel",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/AppleBarrel"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/root-list": {
-      "get": {
-        "operationId": "xml_getRootList",
-        "description": "Gets a list as the root element.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The root list.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "type": "array",
-                  "xml": {
-                    "name": "bananas"
-                  },
-                  "items": {
-                    "$ref": "#/components/schemas/Banana"
-                  }
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putRootList",
-        "description": "Puts a list as the root element.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "bananas",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "type": "array",
-                "xml": {
-                  "name": "bananas",
-                  "wrapped": true
-                },
-                "items": {
-                  "$ref": "#/components/schemas/Banana"
-                }
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/root-list-single-item": {
-      "get": {
-        "operationId": "xml_getRootListSingleItem",
-        "description": "Gets a list with a single item.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The root list.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "type": "array",
-                  "xml": {
-                    "name": "bananas"
-                  },
-                  "items": {
-                    "$ref": "#/components/schemas/Banana"
-                  }
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putRootListSingleItem",
-        "description": "Puts a list with a single item.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "bananas",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "type": "array",
-                "xml": {
-                  "name": "bananas",
-                  "wrapped": true
-                },
-                "items": {
-                  "$ref": "#/components/schemas/Banana"
-                }
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/empty-root-list": {
-      "get": {
-        "operationId": "xml_getEmptyRootList",
-        "description": "Gets an empty list as the root element.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The root list.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "type": "array",
-                  "xml": {
-                    "name": "bananas"
-                  },
-                  "items": {
-                    "$ref": "#/components/schemas/Banana"
-                  }
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putEmptyRootList",
-        "description": "Puts an empty list as the root element.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "bananas",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "type": "array",
-                "xml": {
-                  "name": "bananas",
-                  "wrapped": true
-                },
-                "items": {
-                  "$ref": "#/components/schemas/Banana"
-                }
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/empty-child-element": {
-      "get": {
-        "operationId": "xml_getEmptyChildElement",
-        "description": "Gets an XML document with an empty child element.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "200": {
-            "description": "The unknown banana.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/Banana"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putEmptyChildElement",
-        "description": "Puts a value with an empty child element.",
-        "tags": ["XML Operations"],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "banana",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/Banana"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/?comp=list": {
-      "get": {
-        "operationId": "xml_listContainers",
-        "description": "Lists containers in a storage account.",
-        "tags": ["XML Operations"],
-        "parameters": [
-          {
-            "name": "comp",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["list"]
-            }
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "A list of containers.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/ListContainersResponse"
-                }
-              }
-            }
-          }
-        }
-      }
-    },
-    "/xml/?comp=properties&restype=service": {
-      "get": {
-        "operationId": "xml_getServiceProperties",
-        "description": "Gets storage service properties.",
-        "tags": ["XML Operations"],
-        "parameters": [
-          {
-            "name": "comp",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["properties"]
-            }
-          },
-          {
-            "name": "restype",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["service"]
-            }
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "The storage service properties.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/StorageServiceProperties"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putServiceProperties",
-        "description": "Puts storage service properties.",
-        "tags": ["XML Operations"],
-        "parameters": [
-          {
-            "name": "comp",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["properties"]
-            }
-          },
-          {
-            "name": "restype",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["service"]
-            }
-          }
-        ],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "properties",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/StorageServiceProperties"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/mycontainer?comp=acl&restype=container": {
-      "get": {
-        "operationId": "xml_getAcls",
-        "description": "Gets storage ACLs for a container.",
-        "tags": ["XML Operations"],
-        "parameters": [
-          {
-            "name": "comp",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["acl"]
-            }
-          },
-          {
-            "name": "restype",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["container"]
-            }
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "The storage ACLs.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/SignedIdentifiers"
-                }
-              }
-            }
-          }
-        }
-      },
-      "put": {
-        "operationId": "xml_putAcls",
-        "description": "Puts storage ACLs for a container.",
-        "tags": ["XML Operations"],
-        "parameters": [
-          {
-            "name": "comp",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["acl"]
-            }
-          },
-          {
-            "name": "restype",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["container"]
-            }
-          }
-        ],
-        "responses": {
-          "201": {
-            "description": "Indicates success."
-          }
-        },
-        "requestBody": {
-          "x-ms-requestBody-name": "properties",
-          "content": {
-            "application/xml": {
-              "schema": {
-                "$ref": "#/components/schemas/SignedIdentifiers"
-              }
-            }
-          },
-          "required": true
-        }
-      }
-    },
-    "/xml/mycontainer?comp=list&restype=container": {
-      "get": {
-        "operationId": "xml_listBlobs",
-        "description": "Lists blobs in a storage container.",
-        "tags": ["XML Operations"],
-        "parameters": [
-          {
-            "name": "comp",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["list"]
-            }
-          },
-          {
-            "name": "restype",
-            "in": "query",
-            "required": true,
-            "schema": {
-              "type": "string",
-              "enum": ["container"]
-            }
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "A list of containers.",
-            "content": {
-              "application/xml": {
-                "schema": {
-                  "$ref": "#/components/schemas/ListBlobsResponse"
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  },
-  "components": {
-    "schemas": {
-      "Error": {
-        "properties": {
-          "status": {
-            "type": "integer",
-            "format": "int32"
-          },
-          "message": {
-            "type": "string"
-          }
-        }
-      },
-      "Slide": {
-        "type": "object",
-        "description": "A slide in a slideshow",
-        "properties": {
-          "type": {
-            "type": "string",
-            "xml": {
-              "attribute": true
-            }
-          },
-          "title": {
-            "type": "string"
-          },
-          "items": {
-            "type": "array",
-            "items": {
-              "type": "string",
-              "xml": {
-                "name": "item"
-              }
-            }
-          }
-        },
-        "xml": {
-          "name": "slide"
-        }
-      },
-      "Slideshow": {
-        "type": "object",
-        "description": "Data about a slideshow",
-        "xml": {
-          "name": "slideshow"
-        },
-        "properties": {
-          "title": {
-            "type": "string",
-            "xml": {
-              "attribute": true
-            }
-          },
-          "date": {
-            "type": "string",
-            "xml": {
-              "attribute": true
-            }
-          },
-          "author": {
-            "type": "string",
-            "xml": {
-              "attribute": true
-            }
-          },
-          "slides": {
-            "type": "array",
-            "items": {
-              "$ref": "#/components/schemas/Slide"
-            }
-          }
-        }
-      },
-      "AppleBarrel": {
-        "type": "object",
-        "description": "A barrel of apples.",
-        "properties": {
-          "GoodApples": {
-            "type": "array",
-            "items": {
-              "type": "string",
-              "xml": {
-                "name": "Apple"
-              }
-            },
-            "xml": {
-              "wrapped": true
-            }
-          },
-          "BadApples": {
-            "type": "array",
-            "items": {
-              "type": "string",
-              "xml": {
-                "name": "Apple"
-              }
-            },
-            "xml": {
-              "wrapped": true
-            }
-          }
-        }
-      },
-      "Banana": {
-        "type": "object",
-        "description": "A banana.",
-        "properties": {
-          "name": {
-            "type": "string",
-            "xml": {
-              "name": "name"
-            }
-          },
-          "flavor": {
-            "type": "string",
-            "xml": {
-              "name": "flavor"
-            }
-          },
-          "expiration": {
-            "description": "The time at which you should reconsider eating this banana",
-            "type": "string",
-            "format": "date-time",
-            "xml": {
-              "name": "expiration"
-            }
-          }
-        },
-        "xml": {
-          "name": "banana"
-        }
-      },
-      "Container": {
-        "type": "object",
-        "required": ["Name", "Properties"],
-        "description": "An Azure Storage container",
-        "properties": {
-          "Name": {
-            "type": "string"
-          },
-          "Properties": {
-            "$ref": "#/components/schemas/ContainerProperties"
-          },
-          "Metadata": {
-            "$ref": "#/components/schemas/Metadata"
-          }
-        }
-      },
-      "ContainerProperties": {
-        "type": "object",
-        "required": ["Last-Modified", "Etag"],
-        "description": "Properties of a container",
-        "properties": {
-          "Last-Modified": {
-            "type": "string",
-            "format": "date-time-rfc1123"
-          },
-          "Etag": {
-            "type": "string",
-            "format": "etag"
-          },
-          "LeaseStatus": {
-            "$ref": "#/components/schemas/LeaseStatus"
-          },
-          "LeaseState": {
-            "$ref": "#/components/schemas/LeaseState"
-          },
-          "LeaseDuration": {
-            "$ref": "#/components/schemas/LeaseDuration"
-          },
-          "PublicAccess": {
-            "$ref": "#/components/schemas/PublicAccessType"
-          }
-        }
-      },
-      "ListContainersResponse": {
-        "xml": {
-          "name": "EnumerationResults"
-        },
-        "description": "An enumeration of containers",
-        "type": "object",
-        "required": ["ServiceEndpoint", "Prefix", "MaxResults", "NextMarker"],
-        "properties": {
-          "ServiceEndpoint": {
-            "type": "string",
-            "xml": {
-              "attribute": true
-            }
-          },
-          "Prefix": {
-            "type": "string"
-          },
-          "Marker": {
-            "type": "string"
-          },
-          "MaxResults": {
-            "type": "integer"
-          },
-          "Containers": {
-            "xml": {
-              "wrapped": true
-            },
-            "type": "array",
-            "items": {
-              "$ref": "#/components/schemas/Container"
-            }
-          },
-          "NextMarker": {
-            "type": "string"
-          }
-        }
-      },
-      "CorsRule": {
-        "description": "CORS is an HTTP feature that enables a web application running under one domain to access resources in another domain. Web browsers implement a security restriction known as same-origin policy that prevents a web page from calling APIs in a different domain; CORS provides a secure way to allow one domain (the origin domain) to call APIs in another domain",
-        "type": "object",
-        "required": ["AllowedOrigins", "AllowedMethods", "AllowedHeaders", "ExposedHeaders", "MaxAgeInSeconds"],
-        "properties": {
-          "AllowedOrigins": {
-            "description": "The origin domains that are permitted to make a request against the storage service via CORS. The origin domain is the domain from which the request originates. Note that the origin must be an exact case-sensitive match with the origin that the user age sends to the service. You can also use the wildcard character '*' to allow all origin domains to make requests via CORS.",
-            "type": "string"
-          },
-          "AllowedMethods": {
-            "description": "The methods (HTTP request verbs) that the origin domain may use for a CORS request. (comma separated)",
-            "type": "string"
-          },
-          "AllowedHeaders": {
-            "description": "the request headers that the origin domain may specify on the CORS request.",
-            "type": "string"
-          },
-          "ExposedHeaders": {
-            "description": "The response headers that may be sent in the response to the CORS request and exposed by the browser to the request issuer",
-            "type": "string"
-          },
-          "MaxAgeInSeconds": {
-            "description": "The maximum amount time that a browser should cache the preflight OPTIONS request.",
-            "type": "integer",
-            "minimum": 0
-          }
-        },
-        "xml": {
-          "name": "CorsRule"
-        }
-      },
-      "Blob": {
-        "description": "An Azure Storage blob",
-        "type": "object",
-        "required": ["Name", "Deleted", "Snapshot", "Properties"],
-        "properties": {
-          "Name": {
-            "type": "string"
-          },
-          "Deleted": {
-            "type": "boolean"
-          },
-          "Snapshot": {
-            "type": "string"
-          },
-          "Properties": {
-            "$ref": "#/components/schemas/BlobProperties"
-          },
-          "Metadata": {
-            "$ref": "#/components/schemas/Metadata"
-          }
-        },
-        "xml": {
-          "name": "Blob"
-        }
-      },
-      "BlobProperties": {
-        "description": "Properties of a blob",
-        "type": "object",
-        "required": ["Etag", "Last-Modified"],
-        "properties": {
-          "Last-Modified": {
-            "type": "string",
-            "format": "date-time-rfc1123"
-          },
-          "Etag": {
-            "type": "string",
-            "format": "etag"
-          },
-          "Content-Length": {
-            "type": "integer",
-            "format": "int64",
-            "description": "Size in bytes"
-          },
-          "Content-Type": {
-            "type": "string"
-          },
-          "Content-Encoding": {
-            "type": "string"
-          },
-          "Content-Language": {
-            "type": "string"
-          },
-          "Content-MD5": {
-            "type": "string"
-          },
-          "Content-Disposition": {
-            "type": "string"
-          },
-          "Cache-Control": {
-            "type": "string"
-          },
-          "x-ms-blob-sequence-number": {
-            "x-ms-client-name": "blobSequenceNumber",
-            "type": "integer"
-          },
-          "BlobType": {
-            "type": "string",
-            "enum": ["BlockBlob", "PageBlob", "AppendBlob"],
-            "x-ms-enum": {
-              "name": "BlobType",
-              "modelAsString": false
-            }
-          },
-          "LeaseStatus": {
-            "$ref": "#/components/schemas/LeaseStatus"
-          },
-          "LeaseState": {
-            "$ref": "#/components/schemas/LeaseState"
-          },
-          "LeaseDuration": {
-            "$ref": "#/components/schemas/LeaseDuration"
-          },
-          "CopyId": {
-            "type": "string"
-          },
-          "CopyStatus": {
-            "$ref": "#/components/schemas/CopyStatus"
-          },
-          "CopySource": {
-            "type": "string"
-          },
-          "CopyProgress": {
-            "type": "string"
-          },
-          "CopyCompletionTime": {
-            "type": "string",
-            "format": "date-time-rfc1123"
-          },
-          "CopyStatusDescription": {
-            "type": "string"
-          },
-          "ServerEncrypted": {
-            "type": "boolean"
-          },
-          "IncrementalCopy": {
-            "type": "boolean"
-          },
-          "DestinationSnapshot": {
-            "type": "string"
-          },
-          "DeletedTime": {
-            "type": "string",
-            "format": "date-time-rfc1123"
-          },
-          "RemainingRetentionDays": {
-            "type": "integer"
-          },
-          "AccessTier": {
-            "$ref": "#/components/schemas/AccessTier"
-          },
-          "AccessTierInferred": {
-            "type": "boolean"
-          },
-          "ArchiveStatus": {
-            "$ref": "#/components/schemas/ArchiveStatus"
-          }
-        }
-      },
-      "Logging": {
-        "description": "Azure Analytics Logging settings.",
-        "type": "object",
-        "required": ["Version", "Delete", "Read", "Write", "RetentionPolicy"],
-        "properties": {
-          "Version": {
-            "description": "The version of Storage Analytics to configure.",
-            "type": "string"
-          },
-          "Delete": {
-            "description": "Indicates whether all delete requests should be logged.",
-            "type": "boolean"
-          },
-          "Read": {
-            "description": "Indicates whether all read requests should be logged.",
-            "type": "boolean"
-          },
-          "Write": {
-            "description": "Indicates whether all write requests should be logged.",
-            "type": "boolean"
-          },
-          "RetentionPolicy": {
-            "$ref": "#/components/schemas/RetentionPolicy"
-          }
-        }
-      },
-      "Metadata": {
-        "type": "object",
-        "additionalProperties": {
-          "type": "string"
-        }
-      },
-      "Metrics": {
-        "description": "",
-        "required": ["Enabled"],
-        "properties": {
-          "Version": {
-            "description": "The version of Storage Analytics to configure.",
-            "type": "string"
-          },
-          "Enabled": {
-            "description": "Indicates whether metrics are enabled for the Blob service.",
-            "type": "boolean"
-          },
-          "IncludeAPIs": {
-            "description": "Indicates whether metrics should generate summary statistics for called API operations.",
-            "type": "boolean"
-          },
-          "RetentionPolicy": {
-            "$ref": "#/components/schemas/RetentionPolicy"
-          }
-        }
-      },
-      "ListBlobsResponse": {
-        "xml": {
-          "name": "EnumerationResults"
-        },
-        "description": "An enumeration of blobs",
-        "type": "object",
-        "required": [
-          "ServiceEndpoint",
-          "ContainerName",
-          "Prefix",
-          "Marker",
-          "MaxResults",
-          "Delimiter",
-          "Blobs",
-          "NextMarker"
-        ],
-        "properties": {
-          "ServiceEndpoint": {
-            "type": "string",
-            "xml": {
-              "attribute": true
-            }
-          },
-          "ContainerName": {
-            "type": "string",
-            "xml": {
-              "attribute": true
-            }
-          },
-          "Prefix": {
-            "type": "string"
-          },
-          "Marker": {
-            "type": "string"
-          },
-          "MaxResults": {
-            "type": "integer"
-          },
-          "Delimiter": {
-            "type": "string"
-          },
-          "Blobs": {
-            "$ref": "#/components/schemas/Blobs"
-          },
-          "NextMarker": {
-            "type": "string"
-          }
-        }
-      },
-      "Blobs": {
-        "type": "object",
-        "properties": {
-          "BlobPrefix": {
-            "type": "array",
-            "items": {
-              "$ref": "#/components/schemas/BlobPrefix"
-            }
-          },
-          "Blob": {
-            "type": "array",
-            "items": {
-              "$ref": "#/components/schemas/Blob"
-            }
-          }
-        }
-      },
-      "BlobPrefix": {
-        "type": "object",
-        "required": ["Name"],
-        "properties": {
-          "Name": {
-            "type": "string"
-          }
-        }
-      },
-      "PublicAccessType": {
-        "type": "string",
-        "enum": ["container", "blob"],
-        "x-ms-enum": {
-          "name": "PublicAccessType",
-          "modelAsString": true
-        }
-      },
-      "CopyStatus": {
-        "type": "string",
-        "enum": ["pending", "success", "aborted", "failed"],
-        "x-ms-enum": {
-          "name": "CopyStatusType",
-          "modelAsString": false
-        }
-      },
-      "LeaseDuration": {
-        "type": "string",
-        "enum": ["infinite", "fixed"],
-        "x-ms-enum": {
-          "name": "LeaseDurationType",
-          "modelAsString": false
-        }
-      },
-      "LeaseState": {
-        "type": "string",
-        "enum": ["available", "leased", "expired", "breaking", "broken"],
-        "x-ms-enum": {
-          "name": "LeaseStateType",
-          "modelAsString": false
-        }
-      },
-      "LeaseStatus": {
-        "type": "string",
-        "enum": ["locked", "unlocked"],
-        "x-ms-enum": {
-          "name": "LeaseStatusType",
-          "modelAsString": false
-        }
-      },
-      "AccessPolicy": {
-        "type": "object",
-        "required": ["Start", "Expiry", "Permission"],
-        "description": "An Access policy",
-        "properties": {
-          "Start": {
-            "description": "the date-time the policy is active",
-            "type": "string",
-            "format": "date-time"
-          },
-          "Expiry": {
-            "description": "the date-time the policy expires",
-            "type": "string",
-            "format": "date-time"
-          },
-          "Permission": {
-            "description": "the permissions for the acl policy",
-            "type": "string"
-          }
-        }
-      },
-      "AccessTier": {
-        "type": "string",
-        "enum": ["P4", "P6", "P10", "P20", "P30", "P40", "P50", "Hot", "Cool", "Archive"],
-        "x-ms-enum": {
-          "name": "AccessTier",
-          "modelAsString": true
-        }
-      },
-      "ArchiveStatus": {
-        "type": "string",
-        "enum": ["rehydrate-pending-to-hot", "rehydrate-pending-to-cool"],
-        "x-ms-enum": {
-          "name": "ArchiveStatus",
-          "modelAsString": true
-        }
-      },
-      "RetentionPolicy": {
-        "description": "the retention policy",
-        "type": "object",
-        "required": ["Enabled"],
-        "properties": {
-          "Enabled": {
-            "description": "Indicates whether a retention policy is enabled for the storage service",
-            "type": "boolean"
-          },
-          "Days": {
-            "description": "Indicates the number of days that metrics or logging or soft-deleted data should be retained. All data older than this value will be deleted",
-            "type": "integer",
-            "minimum": 1
-          }
-        }
-      },
-      "SignedIdentifier": {
-        "description": "signed identifier",
-        "type": "object",
-        "required": ["Id", "AccessPolicy"],
-        "properties": {
-          "Id": {
-            "type": "string",
-            "description": "a unique id"
-          },
-          "AccessPolicy": {
-            "description": "The access policy",
-            "$ref": "#/components/schemas/AccessPolicy"
-          }
-        },
-        "xml": {
-          "name": "SignedIdentifier"
-        }
-      },
-      "SignedIdentifiers": {
-        "description": "a collection of signed identifiers",
-        "type": "array",
-        "items": {
-          "$ref": "#/components/schemas/SignedIdentifier"
-        },
-        "xml": {
-          "wrapped": true,
-          "name": "SignedIdentifiers"
-        }
-      },
-      "StorageServiceProperties": {
-        "description": "Storage Service Properties.",
-        "type": "object",
-        "properties": {
-          "Logging": {
-            "description": "Azure Analytics Logging settings",
-            "$ref": "#/components/schemas/Logging"
-          },
-          "HourMetrics": {
-            "description": "A summary of request statistics grouped by API in hourly aggregates for blobs",
-            "$ref": "#/components/schemas/Metrics"
-          },
-          "MinuteMetrics": {
-            "description": "a summary of request statistics grouped by API in minute aggregates for blobs",
-            "$ref": "#/components/schemas/Metrics"
-          },
-          "Cors": {
-            "description": "The set of CORS rules.",
-            "type": "array",
-            "items": {
-              "$ref": "#/components/schemas/CorsRule"
-            },
-            "xml": {
-              "wrapped": true
-            }
-          },
-          "DefaultServiceVersion": {
-            "description": "The default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions",
-            "type": "string"
-          },
-          "DeleteRetentionPolicy": {
-            "description": "The Delete Retention Policy for the service",
-            "$ref": "#/components/schemas/RetentionPolicy"
-          }
-        }
-      },
-      "ComplexTypeNoMeta": {
-        "description": "I am a complex type with no XML node",
-        "type": "object",
-        "properties": {
-          "ID": {
-            "description": "The id of the res",
-            "type": "string"
-          }
-        }
-      },
-      "ComplexTypeWithMeta": {
-        "description": "I am a complex type with XML node",
-        "type": "object",
-        "properties": {
-          "ID": {
-            "description": "The id of the res",
-            "type": "string"
-          }
-        },
-        "xml": {
-          "name": "XMLComplexTypeWithMeta"
-        }
-      },
-      "RootWithRefAndNoMeta": {
-        "description": "I am root, and I ref a model with no meta",
-        "type": "object",
-        "properties": {
-          "RefToModel": {
-            "description": "XML will use RefToModel",
-            "$ref": "#/components/schemas/ComplexTypeNoMeta"
-          },
-          "Something": {
-            "description": "Something else (just to avoid flattening)",
-            "type": "string"
-          }
-        }
-      },
-      "RootWithRefAndMeta": {
-        "description": "I am root, and I ref a model WITH meta",
-        "type": "object",
-        "properties": {
-          "RefToModel": {
-            "description": "XML will use XMLComplexTypeWithMeta",
-            "$ref": "#/components/schemas/ComplexTypeWithMeta"
-          },
-          "Something": {
-            "description": "Something else (just to avoid flattening)",
-            "type": "string"
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/gen_test.go b/gen_test.go
index 9fb213e8..c15f22bc 100644
--- a/gen_test.go
+++ b/gen_test.go
@@ -73,9 +73,6 @@ func TestGenerate(t *testing.T) {
 		},
 		"autorest/lro.json":     {},
 		"autorest/storage.json": {},
-		"autorest/xml-service.json": {
-			"unsupported content types",
-		},
 		"autorest/xms-error-responses.json": {},
 		"2ch.yml": {},
 		"api.github.com.json": {
diff --git a/openapi/parser/path_parser_test.go b/openapi/parser/path_parser_test.go
index 83d64a81..83b4b74a 100644
--- a/openapi/parser/path_parser_test.go
+++ b/openapi/parser/path_parser_test.go
@@ -57,11 +57,21 @@ func TestPathParser(t *testing.T) {
 				{Raw: "abc/def"},
 			},
 		},
+		{
+			Path: "/foo%20bar",
+			Expect: []openapi.PathPart{
+				{Raw: "/foo bar"},
+			},
+		},
 		{
 			Path:      "/foo/{bar}/{baz}",
 			Params:    []*openapi.Parameter{bar},
 			ExpectErr: `path parameter not specified: "baz"`,
 		},
+		{
+			Path:      "/foo/{",
+			ExpectErr: `invalid path: /foo/{`,
+		},
 		{
 			Path:      "/foo/{{",
 			ExpectErr: `invalid path: /foo/{{`,
@@ -78,6 +88,26 @@ func TestPathParser(t *testing.T) {
 			Path:      "/foo/{{}}",
 			ExpectErr: `invalid path: /foo/{{}}`,
 		},
+		{
+			Path:      "/foo/{/",
+			ExpectErr: `invalid path: /foo/{/`,
+		},
+		{
+			Path:      "foo/",
+			ExpectErr: `path MUST begin with a forward slash`,
+		},
+		{
+			Path:      "/foo?k=v",
+			ExpectErr: `path MUST NOT contain a query string`,
+		},
+		{
+			Path:      "/foo#frag",
+			ExpectErr: `path MUST NOT contain a fragment`,
+		},
+		{
+			Path:      "https://i-want-to-die.org/foo",
+			ExpectErr: `path MUST be relative`,
+		},
 	}
 
 	for i, test := range tests {
diff --git a/spec_test.go b/spec_test.go
index 88bbc77b..bba5fe69 100644
--- a/spec_test.go
+++ b/spec_test.go
@@ -26,6 +26,5 @@ func TestPropertiesJSON(t *testing.T) {
 
 	b, err := json.Marshal(props)
 	require.NoError(t, err)
-
 	require.Equal(t, data, string(b))
 }