1
0
Fork 0
mirror of https://github.com/laravel-ls/uri synced 2026-07-04 16:34:11 +02:00

uri: fix {,Un}MarshalJSON and Parse logic

This commit is contained in:
Koichi Shiraishi 2019-06-05 12:50:56 +09:00
parent b45bca663f
commit 2f7ed69ad1
No known key found for this signature in database
GPG key ID: A71DFD3B4DA7A79B

260
uri.go
View file

@ -28,12 +28,12 @@ const (
) )
const ( const (
keyAuthority = "authority"
keyFragment = "fragment"
keyFsPath = "fsPath"
keyPath = "path"
keyQuery = "query"
keyScheme = "scheme" keyScheme = "scheme"
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) http://tools.ietf.org/html/rfc3986.
@ -50,12 +50,17 @@ const (
// / \ / \ // / \ / \
// urn:example:animal:ferret:nose // urn:example:animal:ferret:nose
type URI struct { type URI struct {
// 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'. // Authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.
// The part between the first double slashes and the next slash. // The part between the first double slashes and the next slash.
Authority string `json:"authority"` Authority string `json:"authority"`
// Fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'. // Path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
Fragment string `json:"fragment"` Path string `json:"path"`
// FsPath returns a string representing the corresponding file system path of this URI. // FsPath returns a string representing the corresponding file system path of this URI.
// //
@ -79,18 +84,13 @@ type URI struct {
// namely the server name, would be missing. // 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). // Therefore `URI#fsPath` exists - it's sugar to ease working with URIs that represent files on disk (`file` scheme).
FsPath string `json:"fsPath"` FsPath string `json:"fsPath,omitempty"`
// Path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
Path string `json:"path"`
// Query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'. // Query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
Query string `json:"query"` Query string `json:"query,omitempty"`
// Scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'. // Fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
// Fragment string `json:"fragment,omitempty"`
// The part before the first colon.
Scheme string `json:"scheme"`
formatted string formatted string
skipEncoding bool skipEncoding bool
@ -102,6 +102,14 @@ func (u *URI) MarshalJSON() ([]byte, error) {
buf.WriteString("{") 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) + `: "`) buf.WriteString(`"` + strconv.Quote(keyAuthority) + `: "`)
authority, err := json.Marshal(u.Authority) authority, err := json.Marshal(u.Authority)
if err != nil { if err != nil {
@ -109,22 +117,6 @@ func (u *URI) MarshalJSON() ([]byte, error) {
} }
buf.Write(authority) buf.Write(authority)
buf.WriteString(",")
buf.WriteString(`"` + strconv.Quote(keyFragment) + `: "`)
fragment, err := json.Marshal(u.Fragment)
if err != nil {
return nil, err
}
buf.Write(fragment)
buf.WriteString(",")
buf.WriteString(`"` + strconv.Quote(keyFsPath) + `: "`)
fsPath, err := json.Marshal(u.FsPath)
if err != nil {
return nil, err
}
buf.Write(fsPath)
buf.WriteString(",") buf.WriteString(",")
buf.WriteString(`"` + strconv.Quote(keyPath) + `: "`) buf.WriteString(`"` + strconv.Quote(keyPath) + `: "`)
path, err := json.Marshal(u.Path) path, err := json.Marshal(u.Path)
@ -133,6 +125,7 @@ func (u *URI) MarshalJSON() ([]byte, error) {
} }
buf.Write(path) buf.Write(path)
if u.Query != "" {
buf.WriteString(",") buf.WriteString(",")
buf.WriteString(`"` + strconv.Quote(keyQuery) + `: "`) buf.WriteString(`"` + strconv.Quote(keyQuery) + `: "`)
query, err := json.Marshal(u.Query) query, err := json.Marshal(u.Query)
@ -140,14 +133,27 @@ func (u *URI) MarshalJSON() ([]byte, error) {
return nil, err return nil, err
} }
buf.Write(query) buf.Write(query)
}
if u.FsPath != "" {
buf.WriteString(",") buf.WriteString(",")
buf.WriteString(`"` + strconv.Quote(keyScheme) + `: "`) buf.WriteString(`"` + strconv.Quote(keyFsPath) + `: "`)
scheme, err := json.Marshal(u.Scheme) fsPath, err := json.Marshal(u.FsPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
buf.Write(scheme) 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("}") buf.WriteString("}")
@ -156,12 +162,9 @@ func (u *URI) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (u *URI) UnmarshalJSON(b []byte) error { func (u *URI) UnmarshalJSON(b []byte) error {
var authorityReceived bool
var fragmentReceived bool
var fsPathReceived bool
var pathReceived bool
var queryReceived bool
var schemeReceived bool var schemeReceived bool
var authorityReceived bool
var pathReceived bool
var jm map[string]json.RawMessage var jm map[string]json.RawMessage
if err := json.Unmarshal(b, &jm); err != nil { if err := json.Unmarshal(b, &jm); err != nil {
@ -171,82 +174,64 @@ func (u *URI) UnmarshalJSON(b []byte) error {
// parse all the defined properties // parse all the defined properties
for k, v := range jm { for k, v := range jm {
switch k { switch k {
case keyScheme:
if err := json.Unmarshal([]byte(v), &u.Scheme); err != nil {
return err
}
schemeReceived = true
case keyAuthority: case keyAuthority:
if err := json.Unmarshal([]byte(v), &u.Authority); err != nil { if err := json.Unmarshal([]byte(v), &u.Authority); err != nil {
return err return err
} }
authorityReceived = true authorityReceived = true
case keyFragment:
if err := json.Unmarshal([]byte(v), &u.Fragment); err != nil {
return err
}
fragmentReceived = true
case keyFsPath:
if err := json.Unmarshal([]byte(v), &u.FsPath); err != nil {
return err
}
fsPathReceived = true
case keyPath: case keyPath:
if err := json.Unmarshal([]byte(v), &u.Path); err != nil { if err := json.Unmarshal([]byte(v), &u.Path); err != nil {
return err return err
} }
pathReceived = true pathReceived = true
case keyFsPath:
if err := json.Unmarshal([]byte(v), &u.FsPath); err != nil {
return err
}
case keyQuery: case keyQuery:
if err := json.Unmarshal([]byte(v), &u.Query); err != nil { if err := json.Unmarshal([]byte(v), &u.Query); err != nil {
return err return err
} }
queryReceived = true
case keyScheme: case keyFragment:
if err := json.Unmarshal([]byte(v), &u.Scheme); err != nil { if err := json.Unmarshal([]byte(v), &u.Fragment); err != nil {
return err return err
} }
schemeReceived = true
default: default:
return fmt.Errorf("additional property not allowed: \"" + k + "\"") return fmt.Errorf("additional property not allowed: \"" + k + "\"")
} }
} }
// check if authority (a required property) was received
if !authorityReceived {
return xerrors.Errorf("%q is required but was not present", keyAuthority)
}
// check if fragment (a required property) was received
if !fragmentReceived {
return xerrors.Errorf("%q is required but was not present", keyFragment)
}
// check if fsPath (a required property) was received
if !fsPathReceived {
return xerrors.Errorf("%q is required but was not present", keyFsPath)
}
// check if path (a required property) was received
if !pathReceived {
return xerrors.Errorf("%q is required but was not present", keyPath)
}
// check if query (a required property) was received
if !queryReceived {
return xerrors.Errorf("%q is required but was not present", keyQuery)
}
// check if scheme (a required property) was received // check if scheme (a required property) was received
if !schemeReceived { if !schemeReceived {
return xerrors.Errorf("%q is required but was not present", keyScheme) 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. // String implements fmt.Stringer.
func (u *URI) String() string { func (u URI) String() string {
switch u.Scheme { switch u.Scheme {
case FileScheme, HTTPScheme, HTTPSScheme: case FileScheme, HTTPScheme, HTTPSScheme:
if u.skipEncoding { if u.skipEncoding {
@ -264,53 +249,6 @@ func (u *URI) String() string {
} }
} }
// Parse parses and creates a new URI from uri.
func Parse(s string) (u *URI) {
us, err := url.Parse(s)
if err != nil {
panic(fmt.Sprintf("url.Parse: %#v\n", err))
}
u = &URI{
Scheme: us.Scheme,
Authority: us.Host,
Path: us.Path,
Query: us.Query().Encode(),
Fragment: us.Fragment,
}
if u.Scheme == FileScheme {
u.FsPath = filepath.FromSlash(us.Path)
}
return u
}
// 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.
func From(scheme, authority, path, query, fragment string) (u *URI) {
u = &URI{
Scheme: scheme,
Authority: authority,
Path: path,
Query: query,
Fragment: fragment,
}
if scheme == FileScheme {
u.FsPath = filepath.FromSlash(path)
}
return u
}
var encodeTable = map[byte]string{ var encodeTable = map[byte]string{
Colon: "%3A", // gen-delims Colon: "%3A", // gen-delims
Slash: "%2F", Slash: "%2F",
@ -406,7 +344,7 @@ func encodeMinimal(path string, _ bool) string {
return res return res
} }
func format(uri *URI, skipEncoding bool) string { func format(uri URI, skipEncoding bool) string {
var encoder func(string, bool) string var encoder func(string, bool) string
switch skipEncoding { switch skipEncoding {
case true: case true:
@ -496,3 +434,65 @@ func format(uri *URI, skipEncoding bool) string {
return b.String() 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)
if err != nil {
return nil, xerrors.Errorf("url.Parse: %w\n", err)
}
switch us.Scheme {
case FileScheme:
u = &URI{
Scheme: FileScheme,
Path: us.Path,
FsPath: filepath.FromSlash(us.Path),
}
case HTTPScheme, HTTPSScheme:
u = &URI{
Scheme: us.Scheme,
Authority: us.Host,
Path: us.Path,
Query: us.Query().Encode(),
Fragment: us.Fragment,
}
default:
return nil, xerrors.New("unknown scheme")
}
return u, nil
}
// 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.
func From(scheme, authority, path, query, fragment string) (u *URI) {
switch scheme {
case FileScheme:
u = &URI{
Scheme: FileScheme,
Path: path,
FsPath: filepath.FromSlash(path),
}
case HTTPScheme, HTTPSScheme:
u = &URI{
Scheme: scheme,
Authority: authority,
Path: path,
Query: url.QueryEscape(query),
Fragment: fragment,
}
}
return u
}