blob: b40ad0e55bd8f89b957451bf026445fc4686406a [file] [log] [blame]
// Copyright 2017 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"flag"
"net/url"
"strings"
"go.chromium.org/luci/common/errors"
)
const logDogURLScheme = "logdog"
// StreamAddr is a fully-qualified LogDog stream address.
type StreamAddr struct {
// Host is the LogDog host.
Host string `json:"host,omitempty"`
// Project is the LUCI project name that this log belongs to.
Project ProjectName `json:"project,omitempty"`
// Path is the LogDog stream path.
Path StreamPath `json:"path,omitempty"`
}
var _ flag.Value = (*StreamAddr)(nil)
// Set implements flag.Value
func (s *StreamAddr) Set(v string) error {
a, err := ParseURL(v)
if err != nil {
return err
}
*s = *a
return nil
}
// Validate returns an error if the supplied StreamAddr is not valid.
func (s *StreamAddr) Validate() error {
if s.Host == "" {
return errors.New("cannot have empty Host")
}
if err := s.Project.Validate(); err != nil {
return err
}
if err := s.Path.Validate(); err != nil {
return err
}
return nil
}
// IsZero returns true iff all fields are empty.
func (s *StreamAddr) IsZero() bool {
return s.Host == "" && s.Path == "" && s.Project == ""
}
// String returns a string representation of this address.
func (s *StreamAddr) String() string { return s.URL().String() }
// URL returns a LogDog URL that represents this Stream.
func (s *StreamAddr) URL() *url.URL {
return &url.URL{
Scheme: logDogURLScheme,
Host: s.Host,
Path: strings.Join([]string{"", string(s.Project), string(s.Path)}, "/"),
}
}
// ParseURL parses a LogDog URL into a Stream. If the URL is malformed, or
// if the host, project, or path is invalid, an error will be returned.
//
// A LogDog URL has the form:
// logdog://<host>/<project>/<prefix>/+/<name>
func ParseURL(v string) (*StreamAddr, error) {
u, err := url.Parse(v)
if err != nil {
return nil, errors.Annotate(err, "failed to parse URL").Err()
}
// Validate Scheme.
if u.Scheme != logDogURLScheme {
return nil, errors.Reason("URL scheme %q is not %s", u.Scheme, logDogURLScheme).Err()
}
addr := StreamAddr{
Host: u.Host,
}
parts := strings.SplitN(u.Path, "/", 3)
if len(parts) != 3 || len(parts[0]) != 0 {
return nil, errors.Reason("URL path does not include both project and path components: %s", u.Path).Err()
}
addr.Project, addr.Path = ProjectName(parts[1]), StreamPath(parts[2])
if err := addr.Project.Validate(); err != nil {
return nil, errors.Annotate(err, "invalid project name: %q", addr.Project).Err()
}
if err := addr.Path.Validate(); err != nil {
return nil, errors.Annotate(err, "invalid stream path: %q", addr.Path).Err()
}
return &addr, nil
}