Skip to content

Commit

Permalink
Adding Platforms and Channels APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
kenshaw committed Mar 24, 2024
1 parent 609c220 commit e4a3c19
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 36 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/_example/_example
/_example/_example.exe
/verhist
/verhist.exe
223 changes: 198 additions & 25 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package verhist
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
Expand Down Expand Up @@ -41,48 +40,204 @@ func New(opts ...Option) *Client {
return cl
}

// Versions returns the versions for the os, channel.
func (cl *Client) Versions(ctx context.Context, os, channel string, q ...string) ([]Version, error) {
// Platforms returns platforms.
func (cl *Client) Platforms(ctx context.Context) ([]Platform, error) {
res := new(PlatformsResponse)
if err := grab(ctx, BaseURL+"/v1/chrome/platforms/", cl.Transport, res); err != nil {
return nil, err
}
return res.Platforms, nil
}

// Channels returns channels for the platform type.
func (cl *Client) Channels(ctx context.Context, typ PlatformType) ([]Channel, error) {
res := new(ChannelsResponse)
if err := grab(ctx, BaseURL+"/v1/chrome/platforms/"+typ.String()+"/channels", cl.Transport, res); err != nil {
return nil, err
}
return res.Channels, nil
}

// All returns all channels.
func (cl *Client) All(ctx context.Context) ([]Channel, error) {
return cl.Channels(ctx, All)
}

// Versions returns the versions for the platform, channel.
func (cl *Client) Versions(ctx context.Context, platform, channel string, q ...string) ([]Version, error) {
if len(q) == 0 {
q = []string{
"order_by", "version desc",
}
}
res := new(VersionsResponse)
if err := grab(ctx, BaseURL+"/v1/chrome/platforms/"+os+"/channels/"+channel+"/versions", cl.Transport, res, q...); err != nil {
if err := grab(ctx, BaseURL+"/v1/chrome/platforms/"+platform+"/channels/"+channel+"/versions", cl.Transport, res, q...); err != nil {
return nil, err
}
return res.Versions, nil
}

// UserAgent builds the user agent for the os, channel.
func (cl *Client) UserAgent(ctx context.Context, os, channel string) (string, error) {
versions, err := cl.Versions(ctx, os, channel)
// Latest veturns the latest version for the platform, channel.
func (cl *Client) Latest(ctx context.Context, platform, channel string) (Version, error) {
versions, err := cl.Versions(ctx, platform, channel)
switch {
case err != nil:
return "", err
return Version{}, err
case len(versions) == 0:
return "", errors.New("no versions returned")
return Version{}, ErrNoVersionsReturned
}
return versions[0].UserAgent(os), nil
return versions[0], nil
}

// Option is a version history client option.
type Option func(*Client)
// UserAgent builds the user agent for the platform, channel.
func (cl *Client) UserAgent(ctx context.Context, platform, channel string) (string, error) {
latest, err := cl.Latest(ctx, platform, channel)
if err != nil {
return "", err
}
return latest.UserAgent(platform), nil
}

// WithTransport is a version history client option to set the http transport.
func WithTransport(transport http.RoundTripper) Option {
return func(cl *Client) {
cl.Transport = transport
// PlatformsResponse wraps the platforms API response.
type PlatformsResponse struct {
Platforms []Platform `json:"platforms,omitempty"`
NextPageToken string `json:"nextPageToken,omitempty"`
}

// Platform contains information about a chrome platform.
type Platform struct {
Name string `json:"name,omitempty"`
PlatformType PlatformType `json:"platformType,omitempty"`
}

// PlatformType is a platform type.
type PlatformType string

// Platform types.
const (
All PlatformType = "all"
Android PlatformType = "android"
ChromeOS PlatformType = "chromeos"
Fuchsia PlatformType = "fuchsia"
IOS PlatformType = "ios"
LacrosARM32 PlatformType = "lacros_arm32"
LacrosARM64 PlatformType = "lacros_arm64"
Lacros PlatformType = "lacros"
Linux PlatformType = "linux"
MacARM64 PlatformType = "mac_arm64"
Mac PlatformType = "mac"
Webview PlatformType = "webview"
Windows64 PlatformType = "win64"
Windows PlatformType = "win"
)

// String satisfies the [fmt.Stinger] interface.
func (typ PlatformType) String() string {
return strings.ToLower(string(typ))
}

// MarshalText satisfies the [encoding.TextMarshaler] interface.
func (typ PlatformType) MarshalText() ([]byte, error) {
return []byte(typ.String()), nil
}

// UnmarshalText satisfies the [encoding.TextUnmarshaler] interface.
func (typ *PlatformType) UnmarshalText(buf []byte) error {
switch PlatformType(strings.ToLower(string(buf))) {
case All:
*typ = All
case Android:
*typ = Android
case ChromeOS:
*typ = ChromeOS
case Fuchsia:
*typ = Fuchsia
case IOS:
*typ = IOS
case LacrosARM32:
*typ = LacrosARM32
case LacrosARM64:
*typ = LacrosARM64
case Lacros:
*typ = Lacros
case Linux:
*typ = Linux
case MacARM64:
*typ = MacARM64
case Mac:
*typ = Mac
case Webview:
*typ = Webview
case Windows64:
*typ = Windows64
case Windows:
*typ = Windows
default:
return ErrInvalidPlatformType
}
return nil
}

// WithLogf is a version history client option to set a log handler for HTTP
// requests and responses.
func WithLogf(logf interface{}, opts ...httplog.Option) Option {
return func(cl *Client) {
cl.Transport = httplog.NewPrefixedRoundTripLogger(cl.Transport, logf, opts...)
// ChannelsResponse wraps the channels API response.
type ChannelsResponse struct {
Channels []Channel `json:"channels,omitempty"`
NextPageToken string `json:"nextPageToken,omitempty"`
}

// Channel contains information about a chrome channel.
type Channel struct {
Name string `json:"name,omitempty"`
ChannelType ChannelType `json:"channelType,omitempty"`
}

// ChannelType is a channel type.
type ChannelType string

// Channel types.
const (
Beta ChannelType = "beta"
CanaryASAN ChannelType = "canary_asan"
Canary ChannelType = "canary"
Dev ChannelType = "dev"
Extended ChannelType = "extended"
LTS ChannelType = "lts"
LTC ChannelType = "ltc"
Stable ChannelType = "stable"
)

// String satisfies the [fmt.Stinger] interface.
func (typ ChannelType) String() string {
return strings.ToLower(string(typ))
}

// MarshalText satisfies the [encoding.TextMarshaler] interface.
func (typ ChannelType) MarshalText() ([]byte, error) {
return []byte(typ.String()), nil
}

// UnmarshalText satisfies the [encoding.TextUnmarshaler] interface.
func (typ *ChannelType) UnmarshalText(buf []byte) error {
switch ChannelType(strings.ToLower(string(buf))) {
case Beta:
*typ = Beta
case CanaryASAN:
*typ = CanaryASAN
case Canary:
*typ = Canary
case Dev:
*typ = Dev
case Extended:
*typ = Extended
case LTS:
*typ = LTS
case LTC:
*typ = LTC
case Stable:
*typ = Stable
default:
return ErrInvalidChannelType
}
return nil
}

// VersionsResponse wraps the versions API response.
Expand All @@ -97,10 +252,10 @@ type Version struct {
Version string `json:"version,omitempty"`
}

// UserAgent builds the user agent for the
func (ver Version) UserAgent(os string) string {
// UserAgent builds a user agent for the platform.
func (ver Version) UserAgent(platform string) string {
typ := "Windows NT 10.0; Win64; x64"
switch strings.ToLower(os) {
switch strings.ToLower(platform) {
case "linux":
typ = "X11; Linux x86_64"
case "mac", "mac_arm64":
Expand All @@ -113,10 +268,28 @@ func (ver Version) UserAgent(os string) string {
return fmt.Sprintf("Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", typ, v)
}

// Option is a version history client option.
type Option func(*Client)

// WithTransport is a version history client option to set the http transport.
func WithTransport(transport http.RoundTripper) Option {
return func(cl *Client) {
cl.Transport = transport
}
}

// WithLogf is a version history client option to set a log handler for HTTP
// requests and responses.
func WithLogf(logf interface{}, opts ...httplog.Option) Option {
return func(cl *Client) {
cl.Transport = httplog.NewPrefixedRoundTripLogger(cl.Transport, logf, opts...)
}
}

// grab grabs the url and json decodes it.
func grab(ctx context.Context, urlstr string, transport http.RoundTripper, v interface{}, q ...string) error {
if len(q)%2 != 0 {
return errors.New("invalid query")
return ErrInvalidQuery
}
z := make(url.Values)
for i := 0; i < len(q); i += 2 {
Expand Down
66 changes: 64 additions & 2 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,75 @@ package verhist

import (
"context"
"os"
"testing"
)

func TestPlatforms(t *testing.T) {
t.Parallel()
var opts []Option
if os.Getenv("VERBOSE") != "" {
opts = append(opts, WithLogf(t.Logf))
}
platforms, err := Platforms(context.Background(), opts...)
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
for _, platform := range platforms {
t.Logf("name: %s platform: %s", platform.Name, platform.PlatformType)
}
}

func TestChannels(t *testing.T) {
t.Parallel()
var opts []Option
if os.Getenv("VERBOSE") != "" {
opts = append(opts, WithLogf(t.Logf))
}
channels, err := Channels(context.Background(), All, opts...)
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
for _, channel := range channels {
t.Logf("name: %s channel: %s", channel.Name, channel.ChannelType)
}
}

func TestVersions(t *testing.T) {
t.Parallel()
var opts []Option
if os.Getenv("VERBOSE") != "" {
opts = append(opts, WithLogf(t.Logf))
}
versions, err := Versions(context.Background(), "linux", "stable", opts...)
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
for _, version := range versions {
t.Logf("name: %s version: %s", version.Name, version.Version)
}
}

func TestLatest(t *testing.T) {
t.Parallel()
var opts []Option
if os.Getenv("VERBOSE") != "" {
opts = append(opts, WithLogf(t.Logf))
}
latest, err := Latest(context.Background(), "win", "stable", opts...)
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
t.Logf("name: %s version: %s", latest.Name, latest.Version)
}

func TestUserAgent(t *testing.T) {
t.Parallel()
cl := New(WithLogf(t.Logf))
userAgent, err := cl.UserAgent(context.Background(), "linux", "stable")
var opts []Option
if os.Getenv("VERBOSE") != "" {
opts = append(opts, WithLogf(t.Logf))
}
userAgent, err := UserAgent(context.Background(), "linux", "stable", opts...)
switch {
case err != nil:
t.Fatalf("expected no error, got: %v", err)
Expand Down
Loading

0 comments on commit e4a3c19

Please sign in to comment.