client.go raw

   1  // Copyright 2022-2025 The sacloud/api-client-go 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 client
  16  
  17  import (
  18  	"fmt"
  19  	"net/http"
  20  	"runtime"
  21  	"strings"
  22  	"sync"
  23  
  24  	sacloudhttp "github.com/sacloud/go-http"
  25  )
  26  
  27  // DefaultUserAgent デフォルトのユーザーエージェント
  28  var DefaultUserAgent = fmt.Sprintf(
  29  	"api-client-go/v%s (%s/%s; +https://github.com/sacloud/api-client-go) %s",
  30  	Version,
  31  	runtime.GOOS,
  32  	runtime.GOARCH,
  33  	sacloudhttp.DefaultUserAgent,
  34  )
  35  
  36  // Client APIクライアント
  37  type Client struct {
  38  	// APIRootURL APIのリクエスト先URLプレフィックス
  39  	APIRootURL string
  40  
  41  	initOnce sync.Once
  42  	factory  *Factory
  43  }
  44  
  45  func (c *Client) ServerURL() string {
  46  	v := c.APIRootURL
  47  	if !strings.HasSuffix(v, "/") {
  48  		v += "/"
  49  	}
  50  	return v
  51  }
  52  
  53  func (c *Client) NewHttpRequestDoer() HttpRequestDoer {
  54  	return c.factory.NewHttpRequestDoer()
  55  }
  56  
  57  func (c *Client) Options() *Options {
  58  	return c.factory.Options()
  59  }
  60  
  61  func (c *Client) init(params *ClientParams) error {
  62  	var initError error
  63  	c.initOnce.Do(func() {
  64  		var opts []*Options
  65  		// 1: Profile
  66  		if !params.DisableProfile {
  67  			o, err := OptionsFromProfile(params.Profile)
  68  			if err != nil {
  69  				initError = err
  70  				return
  71  			}
  72  			opts = append(opts, o)
  73  		}
  74  
  75  		// 2: Env
  76  		if !params.DisableEnv {
  77  			opts = append(opts, OptionsFromEnv())
  78  		}
  79  
  80  		// 3: UserAgent
  81  		opts = append(opts, &Options{
  82  			UserAgent: params.UserAgent,
  83  		})
  84  
  85  		// 4: Options
  86  		if params.Options != nil {
  87  			opts = append(opts, params.Options)
  88  		}
  89  
  90  		// 5: フィールドのAPIキー
  91  		opts = append(opts, &Options{
  92  			AccessToken:       params.Token,
  93  			AccessTokenSecret: params.Secret,
  94  		})
  95  
  96  		// 6: Custom HTTP client (for httptest)
  97  		if params.HTTPClient != nil {
  98  			opts = append(opts, &Options{
  99  				HttpClient: params.HTTPClient,
 100  			})
 101  		}
 102  
 103  		c.factory = NewFactory(opts...)
 104  	})
 105  	return initError
 106  }
 107  
 108  // SDKライブラリから設定するパラメータ。WithXXXを使って特定のパラメータだけ設定可能
 109  type ClientParams struct {
 110  	// APIのリクエスト先URLプレフィックス
 111  	APIRootURL string
 112  	// APIキー群
 113  	Token  string
 114  	Secret string
 115  	// クライアントから送られるユーザエージェント
 116  	UserAgent string
 117  	// Options HTTPクライアント関連オプション
 118  	Options *Options
 119  	// Profile usacloud互換プロファイル名
 120  	Profile string
 121  	// usacloud互換プロファイルからの設定読み取りを無効化
 122  	DisableProfile bool
 123  	// 環境変数からの設定読み取りを無効化
 124  	DisableEnv bool
 125  	// カスタムHTTPクライアント (例: httptest.NewServer().Client())
 126  	HTTPClient *http.Client
 127  	// SDKライブラリから追加したいパラメータがあったら随時追加
 128  }
 129  
 130  type ClientParam func(*ClientParams)
 131  
 132  func WithUserAgent(ua string) ClientParam {
 133  	return func(params *ClientParams) {
 134  		params.UserAgent = ua
 135  	}
 136  }
 137  
 138  func WithApiKeys(accessToken string, secretToken string) ClientParam {
 139  	return func(params *ClientParams) {
 140  		params.Token = accessToken
 141  		params.Secret = secretToken
 142  	}
 143  }
 144  
 145  func WithProfile(profile string) ClientParam {
 146  	return func(params *ClientParams) {
 147  		params.Profile = profile
 148  	}
 149  }
 150  
 151  func WithDisableProfile(disable bool) ClientParam {
 152  	return func(params *ClientParams) {
 153  		params.DisableProfile = disable
 154  	}
 155  }
 156  
 157  func WithDisableEnv(disable bool) ClientParam {
 158  	return func(params *ClientParams) {
 159  		params.DisableEnv = disable
 160  	}
 161  }
 162  
 163  func WithOptions(options *Options) ClientParam {
 164  	return func(params *ClientParams) {
 165  		params.Options = options
 166  	}
 167  }
 168  
 169  // WithHTTPClient allows setting a custom http.Client (e.g., from httptest)
 170  func WithHTTPClient(client *http.Client) ClientParam {
 171  	return func(params *ClientParams) {
 172  		params.HTTPClient = client
 173  	}
 174  }
 175  
 176  // Clientを初期化してから返す。WithXXXを使って特定の設定を初期化可能
 177  func NewClient(apiUrl string, params ...ClientParam) (*Client, error) {
 178  	clientParams := &ClientParams{
 179  		APIRootURL: apiUrl,
 180  		UserAgent:  DefaultUserAgent,
 181  	}
 182  
 183  	for _, param := range params {
 184  		param(clientParams)
 185  	}
 186  
 187  	return NewClientWithParams(clientParams)
 188  }
 189  
 190  // 設定したいパラメータが多い場合は直接ClientParamsを初期化して渡す
 191  func NewClientWithParams(params *ClientParams) (*Client, error) {
 192  	client := &Client{
 193  		APIRootURL: params.APIRootURL,
 194  	}
 195  	if err := client.init(params); err != nil {
 196  		return nil, err
 197  	}
 198  
 199  	return client, nil
 200  }
 201