From 90a5178f881453c10c4be28d78533f5197335878 Mon Sep 17 00:00:00 2001 From: Koichi Shiraishi Date: Wed, 5 Jun 2019 08:41:34 +0900 Subject: [PATCH] uri: fix URI json tag and add {,Un}MarshalJSON methods --- uri.go | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 215 insertions(+), 16 deletions(-) diff --git a/uri.go b/uri.go index 3dcc99c..950cbc0 100644 --- a/uri.go +++ b/uri.go @@ -5,13 +5,25 @@ package uri import ( + "bytes" + "encoding/json" "fmt" "net/url" "path/filepath" + + "golang.org/x/xerrors" ) -// FileScheme schema of filesystem path. -const FileScheme = "file" +const ( + // FileScheme schema of filesystem path. + FileScheme = "file" + + // HTTPScheme schema of http. + HTTPScheme = "http" + + // HTTPSScheme schema of https. + HTTPSScheme = "https" +) // URI Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986. // @@ -29,10 +41,10 @@ const FileScheme = "file" type URI struct { // 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. - Authority string `json:"authority,omitempty"` + Authority string `json:"authority"` // Fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'. - Fragment string `json:"fragment,omitempty"` + Fragment string `json:"fragment"` // FsPath returns a string representing the corresponding file system path of this URI. // @@ -56,18 +68,212 @@ type URI struct { // 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"` + FsPath string `json:"fsPath"` // Path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'. - Path string `json:"path,omitempty"` + Path string `json:"path"` // Query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'. - Query string `json:"query,omitempty"` + Query string `json:"query"` // Scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'. // // The part before the first colon. - Scheme string `json:"scheme,omitempty"` + Scheme string `json:"scheme"` +} + +// MarshalJSON implements json.Marshaler. +func (u *URI) MarshalJSON() ([]byte, error) { + buf := bytes.NewBuffer(make([]byte, 0)) + buf.WriteString("{") + + comma := false + // "Authority" field is required + // only required object types supported for marshal checking (for now) + // Marshal the "authority" field + if comma { + buf.WriteString(",") + } + + buf.WriteString("\"authority\": ") + if tmp, err := json.Marshal(u.Authority); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + + comma = true + // "Fragment" field is required + // only required object types supported for marshal checking (for now) + // Marshal the "fragment" field + if comma { + buf.WriteString(",") + } + + buf.WriteString("\"fragment\": ") + if tmp, err := json.Marshal(u.Fragment); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + + comma = true + // "FsPath" field is required + // only required object types supported for marshal checking (for now) + // Marshal the "fsPath" field + if comma { + buf.WriteString(",") + } + + buf.WriteString("\"fsPath\": ") + if tmp, err := json.Marshal(u.FsPath); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + + comma = true + // "Path" field is required + // only required object types supported for marshal checking (for now) + // Marshal the "path" field + if comma { + buf.WriteString(",") + } + + buf.WriteString("\"path\": ") + if tmp, err := json.Marshal(u.Path); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + + comma = true + // "Query" field is required + // only required object types supported for marshal checking (for now) + // Marshal the "query" field + if comma { + buf.WriteString(",") + } + + buf.WriteString("\"query\": ") + if tmp, err := json.Marshal(u.Query); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + + comma = true + // "Scheme" field is required + // only required object types supported for marshal checking (for now) + // Marshal the "scheme" field + if comma { + buf.WriteString(",") + } + + buf.WriteString("\"scheme\": ") + if tmp, err := json.Marshal(u.Scheme); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + + comma = true + + buf.WriteString("}") + rv := buf.Bytes() + + return rv, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +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 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 "authority": + if err := json.Unmarshal([]byte(v), &u.Authority); err != nil { + return err + } + authorityReceived = true + + case "fragment": + if err := json.Unmarshal([]byte(v), &u.Fragment); err != nil { + return err + } + fragmentReceived = true + + case "fsPath": + if err := json.Unmarshal([]byte(v), &u.FsPath); err != nil { + return err + } + fsPathReceived = true + + case "path": + if err := json.Unmarshal([]byte(v), &u.Path); err != nil { + return err + } + pathReceived = true + + case "query": + if err := json.Unmarshal([]byte(v), &u.Query); err != nil { + return err + } + queryReceived = true + + case "scheme": + if err := json.Unmarshal([]byte(v), &u.Scheme); err != nil { + return err + } + schemeReceived = true + + default: + return fmt.Errorf("additional property not allowed: \"" + k + "\"") + } + } + + // check if authority (a required property) was received + if !authorityReceived { + return xerrors.New("\"authority\" is required but was not present") + } + + // check if fragment (a required property) was received + if !fragmentReceived { + return xerrors.New("\"fragment\" is required but was not present") + } + + // check if fsPath (a required property) was received + if !fsPathReceived { + return xerrors.New("\"fsPath\" is required but was not present") + } + + // check if path (a required property) was received + if !pathReceived { + return xerrors.New("\"path\" is required but was not present") + } + + // check if query (a required property) was received + if !queryReceived { + return xerrors.New("\"query\" is required but was not present") + } + + // check if scheme (a required property) was received + if !schemeReceived { + return xerrors.New("\"scheme\" is required but was not present") + } + + return nil } // String implements fmt.Stringer. @@ -80,7 +286,7 @@ func (u *URI) String() string { } return uri.String() - case "http", "https": + case HTTPScheme, HTTPSScheme: uri := &url.URL{ Scheme: u.Scheme, Host: u.Authority, @@ -95,13 +301,6 @@ func (u *URI) String() string { } } -// State represents a URI State. -type State struct { - *URI - Mid float64 `json:"$mid,omitempty"` - External string `json:"external,omitempty"` -} - // Parse parses and creates a new URI from uri. func Parse(s string) *URI { u, err := url.Parse(s)