go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/common/types/streamaddr.go (about) 1 // Copyright 2017 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package types 16 17 import ( 18 "flag" 19 "net/url" 20 "strings" 21 22 "go.chromium.org/luci/common/errors" 23 "go.chromium.org/luci/config" 24 ) 25 26 const logDogURLScheme = "logdog" 27 28 // StreamAddr is a fully-qualified LogDog stream address. 29 type StreamAddr struct { 30 // Host is the LogDog host. 31 Host string `json:"host,omitempty"` 32 33 // Project is the LUCI project name that this log belongs to. 34 Project string `json:"project,omitempty"` 35 36 // Path is the LogDog stream path. 37 Path StreamPath `json:"path,omitempty"` 38 } 39 40 var _ flag.Value = (*StreamAddr)(nil) 41 42 // Set implements flag.Value 43 func (s *StreamAddr) Set(v string) error { 44 a, err := ParseURL(v) 45 if err != nil { 46 return err 47 } 48 *s = *a 49 return nil 50 } 51 52 // Validate returns an error if the supplied StreamAddr is not valid. 53 func (s *StreamAddr) Validate() error { 54 if s.Host == "" { 55 return errors.New("cannot have empty Host") 56 } 57 if err := config.ValidateProjectName(s.Project); err != nil { 58 return err 59 } 60 if err := s.Path.Validate(); err != nil { 61 return err 62 } 63 return nil 64 } 65 66 // IsZero returns true iff all fields are empty. 67 func (s *StreamAddr) IsZero() bool { 68 return s.Host == "" && s.Path == "" && s.Project == "" 69 } 70 71 // String returns a string representation of this address. 72 func (s *StreamAddr) String() string { return s.URL().String() } 73 74 // URL returns a LogDog URL that represents this Stream. 75 func (s *StreamAddr) URL() *url.URL { 76 return &url.URL{ 77 Scheme: logDogURLScheme, 78 Host: s.Host, 79 Path: strings.Join([]string{"", string(s.Project), string(s.Path)}, "/"), 80 } 81 } 82 83 // ParseURL parses a LogDog URL into a Stream. If the URL is malformed, or 84 // if the host, project, or path is invalid, an error will be returned. 85 // 86 // A LogDog URL has the form: 87 // logdog://<host>/<project>/<prefix>/+/<name> 88 func ParseURL(v string) (*StreamAddr, error) { 89 u, err := url.Parse(v) 90 if err != nil { 91 return nil, errors.Annotate(err, "failed to parse URL").Err() 92 } 93 94 // Validate Scheme. 95 if u.Scheme != logDogURLScheme { 96 return nil, errors.Reason("URL scheme %q is not %s", u.Scheme, logDogURLScheme).Err() 97 } 98 addr := StreamAddr{ 99 Host: u.Host, 100 } 101 102 parts := strings.SplitN(u.Path, "/", 3) 103 if len(parts) != 3 || len(parts[0]) != 0 { 104 return nil, errors.Reason("URL path does not include both project and path components: %s", u.Path).Err() 105 } 106 107 addr.Project, addr.Path = parts[1], StreamPath(parts[2]) 108 if err := config.ValidateProjectName(addr.Project); err != nil { 109 return nil, errors.Annotate(err, "invalid project name: %q", addr.Project).Err() 110 } 111 112 if err := addr.Path.Validate(); err != nil { 113 return nil, errors.Annotate(err, "invalid stream path: %q", addr.Path).Err() 114 } 115 116 return &addr, nil 117 }