mirror of
https://github.com/laravel-ls/uri
synced 2026-07-04 00:23:39 +02:00
uri: change all implemestations
This commit is contained in:
parent
e2736f1bb7
commit
695121e1f8
2 changed files with 140 additions and 556 deletions
532
uri.go
532
uri.go
|
|
@ -5,13 +5,12 @@
|
||||||
package uri
|
package uri
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
@ -28,15 +27,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
keyScheme = "scheme"
|
hierPart = "://"
|
||||||
keyAuthority = "authority"
|
|
||||||
keyPath = "path"
|
|
||||||
keyFsPath = "fsPath"
|
|
||||||
keyQuery = "query"
|
|
||||||
keyFragment = "fragment"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// URI Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
// URI Uniform Resource Identifier (URI) https://tools.ietf.org/html/rfc3986.
|
||||||
//
|
//
|
||||||
// This class is a simple parser which creates the basic component parts
|
// This class is a simple parser which creates the basic component parts
|
||||||
// (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
// (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
||||||
|
|
@ -49,453 +43,161 @@ const (
|
||||||
// | _____________________|__
|
// | _____________________|__
|
||||||
// / \ / \
|
// / \ / \
|
||||||
// urn:example:animal:ferret:nose
|
// urn:example:animal:ferret:nose
|
||||||
type URI struct {
|
type URI string
|
||||||
// Scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
|
|
||||||
//
|
|
||||||
// The part before the first colon.
|
|
||||||
Scheme string `json:"scheme"`
|
|
||||||
|
|
||||||
// Authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.
|
// Filename returns the file path for the given URI. It will return an error if
|
||||||
// The part between the first double slashes and the next slash.
|
// the URI is invalid, or if the URI does not have the file scheme.
|
||||||
Authority string `json:"authority"`
|
func (uri URI) Filename() (string, error) {
|
||||||
|
filename, err := filename(uri)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// Path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
|
return filepath.FromSlash(filename), nil
|
||||||
Path string `json:"path"`
|
}
|
||||||
|
|
||||||
// FsPath returns a string representing the corresponding file system path of this URI.
|
func filename(uri URI) (string, error) {
|
||||||
//
|
u, err := url.ParseRequestURI(string(uri))
|
||||||
// Will handle UNC paths, normalizes windows drive letters to lower-case, and uses the
|
if err != nil {
|
||||||
// platform specific path separator.
|
return "", xerrors.Errorf("failed to parse request URI: %w", err)
|
||||||
//
|
}
|
||||||
// * Will *not* validate the path for invalid characters and semantics.
|
|
||||||
// * Will *not* look at the scheme of this URI.
|
|
||||||
// * The result shall *not* be used for display purposes but for accessing a file on disk.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// The *difference* to `URI#path` is the use of the platform specific separator and the handling
|
|
||||||
// of UNC paths. See the below sample of a file-uri with an authority (UNC path).
|
|
||||||
//
|
|
||||||
// const u = URI.parse('file://server/c$/folder/file.txt')
|
|
||||||
// u.authority === 'server'
|
|
||||||
// u.path === '/shares/c$/file.txt'
|
|
||||||
// u.fsPath === '\\server\c$\folder\file.txt'
|
|
||||||
//
|
|
||||||
// Using `URI#path` to read a file (using fs-apis) would not be enough because parts of the path,
|
|
||||||
// namely the server name, would be missing.
|
|
||||||
//
|
|
||||||
// Therefore `URI#fsPath` exists - it's sugar to ease working with URIs that represent files on disk (`file` scheme).
|
|
||||||
FsPath string `json:"fsPath,omitempty"`
|
|
||||||
|
|
||||||
// Query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
|
if u.Scheme != FileScheme {
|
||||||
Query string `json:"query,omitempty"`
|
return "", xerrors.Errorf("only file URIs are supported, got %v", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
// Fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
|
if isWindowsDriveURI(u.Path) {
|
||||||
Fragment string `json:"fragment,omitempty"`
|
u.Path = u.Path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
formatted string
|
return u.Path, nil
|
||||||
skipEncoding bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler.
|
// MarshalJSON implements json.Marshaler.
|
||||||
func (u *URI) MarshalJSON() ([]byte, error) {
|
func (u URI) MarshalJSON() ([]byte, error) {
|
||||||
buf := new(bytes.Buffer)
|
return nil, nil
|
||||||
|
|
||||||
buf.WriteString("{")
|
|
||||||
|
|
||||||
buf.WriteString(`"` + strconv.Quote(keyScheme) + `: "`)
|
|
||||||
scheme, err := json.Marshal(u.Scheme)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf.Write(scheme)
|
|
||||||
|
|
||||||
buf.WriteString(",")
|
|
||||||
buf.WriteString(`"` + strconv.Quote(keyAuthority) + `: "`)
|
|
||||||
authority, err := json.Marshal(u.Authority)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf.Write(authority)
|
|
||||||
|
|
||||||
buf.WriteString(",")
|
|
||||||
buf.WriteString(`"` + strconv.Quote(keyPath) + `: "`)
|
|
||||||
path, err := json.Marshal(u.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf.Write(path)
|
|
||||||
|
|
||||||
if u.Query != "" {
|
|
||||||
buf.WriteString(",")
|
|
||||||
buf.WriteString(`"` + strconv.Quote(keyQuery) + `: "`)
|
|
||||||
query, err := json.Marshal(u.Query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf.Write(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.FsPath != "" {
|
|
||||||
buf.WriteString(",")
|
|
||||||
buf.WriteString(`"` + strconv.Quote(keyFsPath) + `: "`)
|
|
||||||
fsPath, err := json.Marshal(u.FsPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf.Write(fsPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Fragment != "" {
|
|
||||||
buf.WriteString(",")
|
|
||||||
buf.WriteString(`"` + strconv.Quote(keyFragment) + `: "`)
|
|
||||||
fragment, err := json.Marshal(u.Fragment)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf.Write(fragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString("}")
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
func (u *URI) UnmarshalJSON(b []byte) error {
|
func (u *URI) UnmarshalJSON(b []byte) error {
|
||||||
var schemeReceived bool
|
|
||||||
var authorityReceived bool
|
|
||||||
var pathReceived bool
|
|
||||||
var jm map[string]json.RawMessage
|
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &jm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse all the defined properties
|
|
||||||
for k, v := range jm {
|
|
||||||
switch k {
|
|
||||||
case keyScheme:
|
|
||||||
if err := json.Unmarshal([]byte(v), &u.Scheme); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
schemeReceived = true
|
|
||||||
|
|
||||||
case keyAuthority:
|
|
||||||
if err := json.Unmarshal([]byte(v), &u.Authority); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authorityReceived = true
|
|
||||||
|
|
||||||
case keyPath:
|
|
||||||
if err := json.Unmarshal([]byte(v), &u.Path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pathReceived = true
|
|
||||||
|
|
||||||
case keyFsPath:
|
|
||||||
if err := json.Unmarshal([]byte(v), &u.FsPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case keyQuery:
|
|
||||||
if err := json.Unmarshal([]byte(v), &u.Query); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case keyFragment:
|
|
||||||
if err := json.Unmarshal([]byte(v), &u.Fragment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("additional property not allowed: \"" + k + "\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if scheme (a required property) was received
|
|
||||||
if !schemeReceived {
|
|
||||||
return xerrors.Errorf("%q is required but was not present", keyScheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if authority (a required property) was received
|
|
||||||
if !authorityReceived {
|
|
||||||
return xerrors.Errorf("%q is required but was not present", keyAuthority)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if path (a required property) was received
|
|
||||||
if !pathReceived {
|
|
||||||
return xerrors.Errorf("%q is required but was not present", keyPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer.
|
// New parses and creates a new URI from s.
|
||||||
func (u *URI) String() string {
|
func New(s string) URI {
|
||||||
switch u.Scheme {
|
if u, err := url.PathUnescape(s); err == nil {
|
||||||
case FileScheme, HTTPScheme, HTTPSScheme:
|
s = u
|
||||||
if u.skipEncoding {
|
|
||||||
return u.formatted
|
|
||||||
}
|
|
||||||
|
|
||||||
u.formatted = format(*u, true)
|
|
||||||
u.skipEncoding = true
|
|
||||||
|
|
||||||
return u.formatted
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "unknown scheme"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(s, FileScheme+hierPart) {
|
||||||
|
return URI(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return File(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
var encodeTable = map[byte]string{
|
// File parses and creates a new filesystem URI from path.
|
||||||
Colon: "%3A", // gen-delims
|
func File(path string) URI {
|
||||||
Slash: "%2F",
|
const goRootPragma = "$GOROOT"
|
||||||
QuestionMark: "%3F",
|
if len(path) >= len(goRootPragma) && strings.EqualFold(goRootPragma, path[:len(goRootPragma)]) {
|
||||||
Hash: "%23",
|
path = runtime.GOROOT() + path[len(goRootPragma):]
|
||||||
OpenSquareBracket: "%5B",
|
}
|
||||||
CloseSquareBracket: "%5D",
|
|
||||||
AtSign: "%40",
|
if !isWindowsDrivePath(path) {
|
||||||
ExclamationMark: "%21", // sub-delims
|
if abs, err := filepath.Abs(path); err == nil {
|
||||||
DollarSign: "%24",
|
path = abs
|
||||||
Ampersand: "%26",
|
}
|
||||||
SingleQuote: "%27",
|
}
|
||||||
OpenParen: "%28",
|
|
||||||
CloseParen: "%29",
|
if isWindowsDrivePath(path) {
|
||||||
Asterisk: "%2A",
|
path = "/" + path
|
||||||
Plus: "%2B",
|
}
|
||||||
Comma: "%2C",
|
|
||||||
Semicolon: "%3B",
|
path = filepath.ToSlash(path)
|
||||||
Equals: "%3D",
|
u := url.URL{
|
||||||
Space: "%20",
|
Scheme: FileScheme,
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
|
||||||
|
return URI(u.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeFast(uriComponent string, allowSlash bool) string {
|
// Parse parses and creates a new URI from s.
|
||||||
b := new(strings.Builder)
|
func Parse(s string) (u URI, err error) {
|
||||||
nativeEncodePos := -1
|
|
||||||
|
|
||||||
for pos := 0; pos < len(uriComponent); pos++ {
|
|
||||||
code := uriComponent[pos]
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case code >= LowerA && code <= LowerZ,
|
|
||||||
code >= UpperA && code <= UpperZ,
|
|
||||||
code >= Digit0 && code <= Digit9,
|
|
||||||
code == Dash,
|
|
||||||
code == Period,
|
|
||||||
code == Underline,
|
|
||||||
code == Tilde,
|
|
||||||
allowSlash && (code == Slash):
|
|
||||||
|
|
||||||
if nativeEncodePos != -1 {
|
|
||||||
b.WriteString(uriComponent[nativeEncodePos:pos])
|
|
||||||
nativeEncodePos = -1
|
|
||||||
}
|
|
||||||
str := b.String()
|
|
||||||
if str != "" {
|
|
||||||
b.WriteString(str)
|
|
||||||
b.WriteString(uriComponent[:pos])
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
str := b.String()
|
|
||||||
if str != "" {
|
|
||||||
b.WriteString(str)
|
|
||||||
b.WriteString(uriComponent[0:pos])
|
|
||||||
}
|
|
||||||
|
|
||||||
escaped := encodeTable[code]
|
|
||||||
if escaped != "" {
|
|
||||||
if nativeEncodePos != -1 {
|
|
||||||
b.WriteString(uriComponent[nativeEncodePos:pos])
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString(escaped)
|
|
||||||
} else if nativeEncodePos == -1 {
|
|
||||||
nativeEncodePos = pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nativeEncodePos != -1 {
|
|
||||||
b.WriteString(uriComponent[:nativeEncodePos])
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeMinimal(path string, _ bool) string {
|
|
||||||
b := new(strings.Builder)
|
|
||||||
|
|
||||||
for i := 0; i < len(path); i++ {
|
|
||||||
code := path[i]
|
|
||||||
if code == Hash || code == QuestionMark {
|
|
||||||
if b.String() == "" {
|
|
||||||
b.WriteString(path[0:i])
|
|
||||||
}
|
|
||||||
b.WriteString(encodeTable[code])
|
|
||||||
} else if b.String() != "" {
|
|
||||||
b.WriteByte(path[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res := b.String()
|
|
||||||
if res == "" {
|
|
||||||
res = path
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func format(uri URI, skipEncoding bool) string {
|
|
||||||
var encoder func(string, bool) string
|
|
||||||
switch skipEncoding {
|
|
||||||
case true:
|
|
||||||
encoder = encodeMinimal
|
|
||||||
case false:
|
|
||||||
encoder = encodeFast
|
|
||||||
}
|
|
||||||
|
|
||||||
b := new(strings.Builder)
|
|
||||||
|
|
||||||
scheme := uri.Scheme
|
|
||||||
if scheme != "" {
|
|
||||||
b.WriteString(scheme)
|
|
||||||
b.WriteByte(':')
|
|
||||||
}
|
|
||||||
|
|
||||||
authority := uri.Authority
|
|
||||||
if authority != "" || scheme == FileScheme {
|
|
||||||
b.WriteRune(filepath.Separator)
|
|
||||||
b.WriteRune(filepath.Separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
if authority != "" {
|
|
||||||
idx := strings.LastIndex(authority, "@")
|
|
||||||
if idx != -1 {
|
|
||||||
// <user>@<auth>
|
|
||||||
userinfo := authority[:idx]
|
|
||||||
authority = authority[idx+1:]
|
|
||||||
|
|
||||||
uiIdx := strings.Index(userinfo, ":")
|
|
||||||
if uiIdx == -1 {
|
|
||||||
b.WriteString(encoder(userinfo, false))
|
|
||||||
} else {
|
|
||||||
// <user>:<pass>@<auth>
|
|
||||||
b.WriteString(encoder(userinfo[:idx], false))
|
|
||||||
b.WriteRune(':')
|
|
||||||
b.WriteString(encoder(userinfo[idx+1:], false))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteRune('@')
|
|
||||||
}
|
|
||||||
|
|
||||||
authority = strings.ToLower(authority)
|
|
||||||
|
|
||||||
idx = strings.Index(authority, ":")
|
|
||||||
if idx == -1 {
|
|
||||||
b.WriteString(encoder(authority, false))
|
|
||||||
} else {
|
|
||||||
// <auth>:<port>
|
|
||||||
b.WriteString(encoder(authority[:idx], false))
|
|
||||||
b.WriteString(authority[idx:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if path := uri.Path; path != "" {
|
|
||||||
// lower-case windows drive letters in /C:/fff or C:/fff
|
|
||||||
if len(path) >= 3 && path[0] == Slash && path[2] == Colon {
|
|
||||||
code := path[1]
|
|
||||||
if code >= UpperA && code <= UpperZ {
|
|
||||||
path = "/" + string(code+32) + ":" + string(path[3]) // "/c:".length == 3
|
|
||||||
}
|
|
||||||
} else if len(path) >= 2 && path[1] == Colon {
|
|
||||||
code := path[0]
|
|
||||||
if code >= UpperA && code <= UpperZ {
|
|
||||||
path = string(code+32) + ":" + string(path[2]) // "/c:".length == 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode the rest of the path
|
|
||||||
b.WriteString(encoder(path, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
if query := uri.Query; query != "" {
|
|
||||||
b.WriteRune('?')
|
|
||||||
b.WriteString(encoder(query, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
if fragment := uri.Fragment; fragment != "" {
|
|
||||||
b.WriteRune('#')
|
|
||||||
|
|
||||||
if skipEncoding {
|
|
||||||
b.WriteString(fragment)
|
|
||||||
} else {
|
|
||||||
b.WriteString(encodeFast(fragment, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses and creates a new URI from uri.
|
|
||||||
func Parse(s string) (u *URI, err error) {
|
|
||||||
us, err := url.Parse(s)
|
us, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("url.Parse: %w\n", err)
|
return u, xerrors.Errorf("url.Parse: %w\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch us.Scheme {
|
switch us.Scheme {
|
||||||
case FileScheme:
|
case FileScheme:
|
||||||
u = &URI{
|
ut := url.URL{
|
||||||
Scheme: FileScheme,
|
Scheme: FileScheme,
|
||||||
Path: us.Path,
|
Path: us.Path,
|
||||||
FsPath: filepath.FromSlash(us.Path),
|
RawPath: filepath.FromSlash(us.Path),
|
||||||
}
|
}
|
||||||
|
u = URI(ut.String())
|
||||||
|
|
||||||
case HTTPScheme, HTTPSScheme:
|
case HTTPScheme, HTTPSScheme:
|
||||||
u = &URI{
|
ut := url.URL{
|
||||||
Scheme: us.Scheme,
|
Scheme: us.Scheme,
|
||||||
Authority: us.Host,
|
Host: us.Host,
|
||||||
Path: us.Path,
|
Path: us.Path,
|
||||||
Query: us.Query().Encode(),
|
RawQuery: us.Query().Encode(),
|
||||||
Fragment: us.Fragment,
|
Fragment: us.Fragment,
|
||||||
}
|
}
|
||||||
|
u = URI(ut.String())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, xerrors.New("unknown scheme")
|
return u, xerrors.New("unknown scheme")
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
return
|
||||||
}
|
|
||||||
|
|
||||||
// File parses and creates a new URI filesystem path from path.
|
|
||||||
func File(path string) *URI {
|
|
||||||
return &URI{
|
|
||||||
Scheme: FileScheme,
|
|
||||||
Path: path,
|
|
||||||
FsPath: filepath.FromSlash(path),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// From returns the new URI from args.
|
// From returns the new URI from args.
|
||||||
func From(scheme, authority, path, query, fragment string) (u *URI) {
|
func From(scheme, authority, path, query, fragment string) URI {
|
||||||
switch scheme {
|
switch scheme {
|
||||||
case FileScheme:
|
case FileScheme:
|
||||||
u = &URI{
|
u := url.URL{
|
||||||
Scheme: FileScheme,
|
Scheme: FileScheme,
|
||||||
Path: path,
|
Path: path,
|
||||||
FsPath: filepath.FromSlash(path),
|
RawPath: filepath.FromSlash(path),
|
||||||
}
|
}
|
||||||
|
return URI(u.String())
|
||||||
|
|
||||||
case HTTPScheme, HTTPSScheme:
|
case HTTPScheme, HTTPSScheme:
|
||||||
u = &URI{
|
u := url.URL{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Authority: authority,
|
Host: authority,
|
||||||
Path: path,
|
Path: path,
|
||||||
Query: url.QueryEscape(query),
|
RawQuery: url.QueryEscape(query),
|
||||||
Fragment: fragment,
|
Fragment: fragment,
|
||||||
}
|
}
|
||||||
}
|
return URI(u.String())
|
||||||
|
|
||||||
return u
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown scheme: %s", scheme))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWindowsDrivePath returns true if the file path is of the form used by Windows.
|
||||||
|
//
|
||||||
|
// We check if the path begins with a drive letter, followed by a ":".
|
||||||
|
func isWindowsDrivePath(path string) bool {
|
||||||
|
if len(path) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return unicode.IsLetter(rune(path[0])) && path[1] == ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWindowsDriveURI returns true if the file URI is of the format used by
|
||||||
|
// Windows URIs. The url.Parse package does not specially handle Windows paths
|
||||||
|
// (see https://golang.org/issue/6027). We check if the URI path has
|
||||||
|
// a drive prefix (e.g. "/C:"). If so, we trim the leading "/".
|
||||||
|
func isWindowsDriveURI(uri string) bool {
|
||||||
|
if len(uri) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
164
uri_test.go
164
uri_test.go
|
|
@ -2,61 +2,34 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package uri_test
|
package uri
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/go-language-server/uri"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
s string
|
s string
|
||||||
want *uri.URI
|
want URI
|
||||||
wantErr bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ValidFileScheme",
|
name: "ValidFileScheme",
|
||||||
s: "file://code.visualstudio.com/docs/extensions/overview.md",
|
s: "file://code.visualstudio.com/docs/extensions/overview.md",
|
||||||
want: &uri.URI{
|
want: URI(FileScheme + hierPart + "/docs/extensions/overview.md"),
|
||||||
Scheme: uri.FileScheme,
|
|
||||||
Path: "/docs/extensions/overview.md",
|
|
||||||
FsPath: filepath.FromSlash("/docs/extensions/overview.md"),
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ValidHTTPScheme",
|
name: "ValidHTTPScheme",
|
||||||
s: "http://code.visualstudio.com/docs/extensions/overview#frag",
|
s: "http://code.visualstudio.com/docs/extensions/overview#frag",
|
||||||
want: &uri.URI{
|
want: URI(HTTPScheme + hierPart + "code.visualstudio.com/docs/extensions/overview#frag"),
|
||||||
Scheme: uri.HTTPScheme,
|
|
||||||
Authority: "code.visualstudio.com",
|
|
||||||
Path: "/docs/extensions/overview",
|
|
||||||
Fragment: "frag",
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ValidHTTPSScheme",
|
name: "ValidHTTPSScheme",
|
||||||
s: "https://code.visualstudio.com/docs/extensions/overview#frag",
|
s: "https://code.visualstudio.com/docs/extensions/overview#frag",
|
||||||
want: &uri.URI{
|
want: URI(HTTPSScheme + hierPart + "code.visualstudio.com/docs/extensions/overview#frag"),
|
||||||
Scheme: uri.HTTPSScheme,
|
|
||||||
Authority: "code.visualstudio.com",
|
|
||||||
Path: "/docs/extensions/overview",
|
|
||||||
Fragment: "frag",
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid",
|
|
||||||
s: "foo://user@example.com:8042/over/there?name=ferret#nose",
|
|
||||||
want: new(uri.URI),
|
|
||||||
wantErr: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
@ -64,12 +37,13 @@ func TestParse(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
got, err := uri.Parse(tt.s)
|
got, err := Parse(tt.s)
|
||||||
if (err != nil) != tt.wantErr {
|
if err != nil {
|
||||||
t.Errorf("wantErr: %t, err: %#v", tt.wantErr, err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(got, tt.want, cmp.AllowUnexported(*tt.want)); (diff != "") != tt.wantErr {
|
|
||||||
|
if diff := cmp.Diff(got, tt.want); diff != "" {
|
||||||
t.Errorf("%s: (-got, +want)\n%s", tt.name, diff)
|
t.Errorf("%s: (-got, +want)\n%s", tt.name, diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -80,23 +54,19 @@ func TestFile(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
want *uri.URI
|
want URI
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ValidFileScheme",
|
name: "ValidFileScheme",
|
||||||
path: "/users/me/c#-projects/",
|
path: "/users/me/c#-projects/",
|
||||||
want: &uri.URI{
|
want: URI(FileScheme + hierPart + "/users/me/c%23-projects"),
|
||||||
Scheme: uri.FileScheme,
|
|
||||||
Path: "/users/me/c#-projects/",
|
|
||||||
FsPath: filepath.FromSlash("/users/me/c#-projects/"),
|
|
||||||
},
|
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid",
|
name: "Invalid",
|
||||||
path: "users-me-c#-projects",
|
path: "users-me-c#-projects",
|
||||||
want: new(uri.URI),
|
want: URI(""),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +75,7 @@ func TestFile(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if diff := cmp.Diff(uri.File(tt.path), tt.want, cmp.AllowUnexported(*tt.want)); (diff != "") != tt.wantErr {
|
if diff := cmp.Diff(File(tt.path), tt.want); (diff != "") != tt.wantErr {
|
||||||
t.Errorf("%s: (-got, +want)\n%s", tt.name, diff)
|
t.Errorf("%s: (-got, +want)\n%s", tt.name, diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -123,7 +93,7 @@ func TestFrom(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *uri.URI
|
want URI
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ValidFileScheme",
|
name: "ValidFileScheme",
|
||||||
|
|
@ -134,11 +104,7 @@ func TestFrom(t *testing.T) {
|
||||||
query: "name=ferret",
|
query: "name=ferret",
|
||||||
fragment: "nose",
|
fragment: "nose",
|
||||||
},
|
},
|
||||||
want: &uri.URI{
|
want: URI(FileScheme + hierPart + "/over/there"),
|
||||||
Scheme: uri.FileScheme,
|
|
||||||
Path: "/over/there",
|
|
||||||
FsPath: filepath.FromSlash("/over/there"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ValidHTTPScheme",
|
name: "ValidHTTPScheme",
|
||||||
|
|
@ -149,13 +115,7 @@ func TestFrom(t *testing.T) {
|
||||||
query: "name=ferret",
|
query: "name=ferret",
|
||||||
fragment: "nose",
|
fragment: "nose",
|
||||||
},
|
},
|
||||||
want: &uri.URI{
|
want: URI(HTTPScheme + hierPart + "example.com:8042/over/there?name%3Dferret#nose"),
|
||||||
Scheme: uri.HTTPScheme,
|
|
||||||
Authority: "example.com:8042",
|
|
||||||
Path: "/over/there",
|
|
||||||
Query: "name%3Dferret",
|
|
||||||
Fragment: "nose",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ValidHTTPSScheme",
|
name: "ValidHTTPSScheme",
|
||||||
|
|
@ -166,13 +126,7 @@ func TestFrom(t *testing.T) {
|
||||||
query: "name=ferret",
|
query: "name=ferret",
|
||||||
fragment: "nose",
|
fragment: "nose",
|
||||||
},
|
},
|
||||||
want: &uri.URI{
|
want: URI(HTTPSScheme + hierPart + "example.com:8042/over/there?name%3Dferret#nose"),
|
||||||
Scheme: uri.HTTPSScheme,
|
|
||||||
Authority: "example.com:8042",
|
|
||||||
Path: "/over/there",
|
|
||||||
Query: "name%3Dferret",
|
|
||||||
Fragment: "nose",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
@ -180,81 +134,9 @@ func TestFrom(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if diff := cmp.Diff(uri.From(tt.args.scheme, tt.args.authority, tt.args.path, tt.args.query, tt.args.fragment), tt.want, cmp.AllowUnexported(*tt.want)); diff != "" {
|
if diff := cmp.Diff(From(tt.args.scheme, tt.args.authority, tt.args.path, tt.args.query, tt.args.fragment), tt.want); diff != "" {
|
||||||
t.Errorf("%s: (-got, +want)\n%s", tt.name, diff)
|
t.Errorf("%s: (-got, +want)\n%s", tt.name, diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestURI_String(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Scheme string
|
|
||||||
Authority string
|
|
||||||
Path string
|
|
||||||
FsPath string
|
|
||||||
Query string
|
|
||||||
Fragment string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "ValidFileScheme",
|
|
||||||
fields: fields{
|
|
||||||
Scheme: string(uri.FileScheme),
|
|
||||||
Path: "docs/extensions/overview.md",
|
|
||||||
FsPath: filepath.FromSlash("docs/extensions/overview.md"),
|
|
||||||
},
|
|
||||||
want: "file://docs/extensions/overview.md",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ValidHTTPScheme",
|
|
||||||
fields: fields{
|
|
||||||
Scheme: string(uri.HTTPScheme),
|
|
||||||
Authority: "code.visualstudio.com",
|
|
||||||
Path: "/docs/extensions/overview",
|
|
||||||
FsPath: filepath.FromSlash("/docs/extensions/overview"),
|
|
||||||
Query: "test",
|
|
||||||
Fragment: "frag",
|
|
||||||
},
|
|
||||||
want: "http://code.visualstudio.com/docs/extensions/overview?test#frag",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ValidHTTPSScheme",
|
|
||||||
fields: fields{
|
|
||||||
Scheme: string(uri.HTTPSScheme),
|
|
||||||
Authority: "code.visualstudio.com",
|
|
||||||
Path: "/docs/extensions/overview",
|
|
||||||
FsPath: filepath.FromSlash("/docs/extensions/overview"),
|
|
||||||
Query: "test",
|
|
||||||
Fragment: "frag",
|
|
||||||
},
|
|
||||||
want: "https://code.visualstudio.com/docs/extensions/overview?test#frag",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
u := &uri.URI{
|
|
||||||
Authority: tt.fields.Authority,
|
|
||||||
Fragment: tt.fields.Fragment,
|
|
||||||
FsPath: tt.fields.FsPath,
|
|
||||||
Path: tt.fields.Path,
|
|
||||||
Query: tt.fields.Query,
|
|
||||||
Scheme: tt.fields.Scheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := u.String(); !cmp.Equal(got, tt.want) {
|
|
||||||
t.Errorf("URI.String() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
if got2 := u.String(); !cmp.Equal(got2, tt.want) { // cache with u.formatted
|
|
||||||
t.Errorf("URI.String() = %v, want %v", got2, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue