feat(first): First commit with basic implementation. Missing test
Some checks failed
continuous-integration/drone Build is failing
Some checks failed
continuous-integration/drone Build is failing
This commit is contained in:
commit
05ac49fd68
116
.drone.yml
Normal file
116
.drone.yml
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: test-pipeline
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: environment
|
||||||
|
image: golang:1.19
|
||||||
|
commands:
|
||||||
|
- go version
|
||||||
|
- go env
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
|
||||||
|
- name: tools
|
||||||
|
image: golang:1.19
|
||||||
|
commands:
|
||||||
|
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
|
||||||
|
- go install github.com/tebeka/go2xunit@latest
|
||||||
|
- go install github.com/t-yuki/gocover-cobertura@latest
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
depends_on:
|
||||||
|
- environment
|
||||||
|
|
||||||
|
- name: tidy
|
||||||
|
image: golang:1.19
|
||||||
|
commands:
|
||||||
|
- go mod tidy
|
||||||
|
- git diff --exit-code -- go.mod go.sum
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
depends_on:
|
||||||
|
- tools
|
||||||
|
|
||||||
|
- name: lint
|
||||||
|
image: golang:1.19
|
||||||
|
commands:
|
||||||
|
- echo 'Running linting'
|
||||||
|
- golangci-lint run
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
depends_on:
|
||||||
|
- tools
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
image: golang:1.19
|
||||||
|
commands:
|
||||||
|
- go test -cover -v ./...
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
path: /go
|
||||||
|
depends_on:
|
||||||
|
- tools
|
||||||
|
|
||||||
|
- name: send telegram notification
|
||||||
|
image: appleboy/drone-telegram
|
||||||
|
settings:
|
||||||
|
token:
|
||||||
|
from_secret: telegram_token
|
||||||
|
to:
|
||||||
|
from_secret: telegram_chat_id
|
||||||
|
message: >
|
||||||
|
{{#success build.status}}
|
||||||
|
✅ Build *#{{build.number}}* of *{{repo.name}}* succeeded.
|
||||||
|
{{else}}
|
||||||
|
❌ Build *#{{build.number}}* of *{{repo.name}}* failed.
|
||||||
|
{{/success}}
|
||||||
|
|
||||||
|
📝 Commit on *{{commit.branch}}*:
|
||||||
|
|
||||||
|
``` {{commit.message}} ```
|
||||||
|
|
||||||
|
🌐 {{ build.link }}
|
||||||
|
format: markdown
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- failure
|
||||||
|
- success
|
||||||
|
depends_on:
|
||||||
|
- test
|
||||||
|
- lint
|
||||||
|
- tidy
|
||||||
|
|
||||||
|
- name: docker
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
repo: registry.dev.m-and-m.ovh/m-and-m/tesla
|
||||||
|
username:
|
||||||
|
from_secret: registry_username
|
||||||
|
password:
|
||||||
|
from_secret: registry_password
|
||||||
|
registry: registry.dev.m-and-m.ovh
|
||||||
|
debug: true
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- success
|
||||||
|
depends_on:
|
||||||
|
- test
|
||||||
|
- lint
|
||||||
|
- tidy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: gopath
|
||||||
|
temp: {}
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/.vscode/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
*.i*
|
210
.golangci.yml
Normal file
210
.golangci.yml
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
---
|
||||||
|
# options for analysis running
|
||||||
|
run:
|
||||||
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
|
timeout: 2m
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude:
|
||||||
|
- ST1000
|
||||||
|
- ST1003
|
||||||
|
- var-naming
|
||||||
|
- package-comments
|
||||||
|
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
|
exclude-rules:
|
||||||
|
# Exclude some linters from running on tests files.
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gocyclo
|
||||||
|
- errcheck
|
||||||
|
- dupl
|
||||||
|
- gosec
|
||||||
|
# Exclude known linters from partially hard-vendored code,
|
||||||
|
# which is impossible to exclude via `nolint` comments.
|
||||||
|
- path: internal/hmac/
|
||||||
|
text: "weak cryptographic primitive"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# Exclude `lll` issues for long lines with `go:generate`.
|
||||||
|
- linters:
|
||||||
|
- lll
|
||||||
|
source: "^//go:generate "
|
||||||
|
|
||||||
|
exclude-use-default: false
|
||||||
|
|
||||||
|
# Maximum count of issues with the same text.
|
||||||
|
# Set to 0 to disable.
|
||||||
|
# Default: 3
|
||||||
|
max-same-issues: 3
|
||||||
|
# Maximum issues count per one linter.
|
||||||
|
# Set to 0 to disable.
|
||||||
|
# Default: 50
|
||||||
|
max-issues-per-linter: 50
|
||||||
|
|
||||||
|
fix: false
|
||||||
|
|
||||||
|
output:
|
||||||
|
sort-results: true
|
||||||
|
|
||||||
|
# Uncomment and add a path if needed to exclude
|
||||||
|
# skip-dirs:
|
||||||
|
# - some/path
|
||||||
|
# skip-files:
|
||||||
|
# - ".*\\.my\\.go$"
|
||||||
|
# - lib/bad.go
|
||||||
|
|
||||||
|
# Find the whole list here https://golangci-lint.run/usage/linters/
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- asciicheck # simple linter to check that your code does not contain non-ASCII identifiers
|
||||||
|
- bidichk
|
||||||
|
- bodyclose # checks whether HTTP response body is closed successfully
|
||||||
|
- containedctx
|
||||||
|
- decorder
|
||||||
|
- depguard
|
||||||
|
- dupl # tool for code clone detection
|
||||||
|
- durationcheck # check for two durations multiplied together
|
||||||
|
- errcheck # checking for unchecked errors in go programs
|
||||||
|
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
|
||||||
|
- execinquery
|
||||||
|
- exhaustive
|
||||||
|
- exportloopref # checks for pointers to enclosing loop variables
|
||||||
|
- goconst # finds repeated strings that could be replaced by a constant
|
||||||
|
- godot
|
||||||
|
- godox # tool for detection of FIXME, TODO and other comment keywords
|
||||||
|
- gofmt
|
||||||
|
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
|
||||||
|
- gomoddirectives # manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
|
||||||
|
- gomodguard # check for blocked dependencies
|
||||||
|
- gosec # inspects source code for security problems
|
||||||
|
- gosimple # linter for Go source code that specializes in simplifying a code
|
||||||
|
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
|
||||||
|
- importas # enforces consistent import aliases
|
||||||
|
- ineffassign # detects when assignments to existing variables are not used
|
||||||
|
- makezero
|
||||||
|
- misspell # finds commonly misspelled English words in comments
|
||||||
|
- nakedret # finds naked returns in functions greater than a specified function length
|
||||||
|
- nilerr # finds the code that returns nil even if it checks that the error is not nil.
|
||||||
|
- nilnil
|
||||||
|
- noctx # noctx finds sending http request without context.Context
|
||||||
|
- nolintlint # reports ill-formed or insufficient nolint directives
|
||||||
|
- nonamedreturns
|
||||||
|
- prealloc # finds slice declarations that could potentially be preallocated
|
||||||
|
# Disable has we have a lot of enum. TODO: find a better way
|
||||||
|
# - revive
|
||||||
|
# Disable because of generic - sqlclosecheck
|
||||||
|
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
|
||||||
|
- stylecheck # a replacement for golint
|
||||||
|
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
|
||||||
|
- unconvert # Remove unnecessary type conversions
|
||||||
|
- unparam # reports unused function parameters
|
||||||
|
- unused # checks Go code for unused constants, variables, functions and types
|
||||||
|
# Disable because of generic - wastedassign # wastedassign finds wasted assignment statements.
|
||||||
|
|
||||||
|
# all available settings of specific linters
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
# report about not checking of errors in type assertions: `a := b.(MyStruct)`;
|
||||||
|
# default is false: such cases aren't reported by default.
|
||||||
|
check-type-assertions: true
|
||||||
|
|
||||||
|
errorlint:
|
||||||
|
# Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats
|
||||||
|
errorf: true
|
||||||
|
# Check for plain type assertions and type switches
|
||||||
|
asserts: true
|
||||||
|
# Check for plain error comparisons
|
||||||
|
comparison: true
|
||||||
|
|
||||||
|
goconst:
|
||||||
|
# minimal length of string constant, 3 by default
|
||||||
|
min-len: 3
|
||||||
|
# minimal occurrences count to trigger, 3 by default
|
||||||
|
min-occurrences: 5
|
||||||
|
|
||||||
|
dupl:
|
||||||
|
# tokens count to trigger issue, 150 by default
|
||||||
|
threshold: 150
|
||||||
|
|
||||||
|
gomoddirectives:
|
||||||
|
# Allow local `replace` directives. Default is false.
|
||||||
|
replace-local: false
|
||||||
|
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com/elastic
|
||||||
|
|
||||||
|
gomodguard:
|
||||||
|
blocked:
|
||||||
|
# List of blocked modules.
|
||||||
|
modules:
|
||||||
|
# Blocked module.
|
||||||
|
- github.com/pkg/errors:
|
||||||
|
# Recommended modules that should be used instead. (Optional)
|
||||||
|
recommendations:
|
||||||
|
- errors
|
||||||
|
- fmt
|
||||||
|
reason: "This package is deprecated, use `fmt.Errorf` with `%w` instead"
|
||||||
|
depguard:
|
||||||
|
list-type:: denylist
|
||||||
|
# Check the list against standard lib.
|
||||||
|
include-go-root: true
|
||||||
|
packages-with-error-message:
|
||||||
|
- io/ioutil: "The package is deprecated, use `io` or `so` instead."
|
||||||
|
|
||||||
|
gosimple:
|
||||||
|
# Select the Go version to target. The default is '1.13'.
|
||||||
|
go: "1.20.7"
|
||||||
|
|
||||||
|
misspell:
|
||||||
|
# Correct spellings using locale preferences for US or UK.
|
||||||
|
# Default is to use a neutral variety of English.
|
||||||
|
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||||
|
# locale: US
|
||||||
|
# ignore-words:
|
||||||
|
# - IdP
|
||||||
|
|
||||||
|
nakedret:
|
||||||
|
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||||
|
max-func-lines: 0
|
||||||
|
|
||||||
|
prealloc:
|
||||||
|
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||||
|
# True by default.
|
||||||
|
simple: true
|
||||||
|
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||||
|
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||||
|
|
||||||
|
nolintlint:
|
||||||
|
# Enable to ensure that nolint directives are all used. Default is true.
|
||||||
|
allow-unused: false
|
||||||
|
# Disable to ensure that nolint directives don't have a leading space. Default is true.
|
||||||
|
allow-leading-space: false
|
||||||
|
# Exclude following linters from requiring an explanation. Default is [].
|
||||||
|
allow-no-explanation: []
|
||||||
|
# Enable to require an explanation of nonzero length after each nolint directive. Default is false.
|
||||||
|
require-explanation: true
|
||||||
|
# Enable to require nolint directives to mention the specific linter being suppressed. Default is false.
|
||||||
|
require-specific: true
|
||||||
|
|
||||||
|
staticcheck:
|
||||||
|
# Select the Go version to target. The default is '1.13'.
|
||||||
|
go: "1.20.7"
|
||||||
|
# https://staticcheck.io/docs/options#checks
|
||||||
|
checks: ["all"]
|
||||||
|
|
||||||
|
stylecheck:
|
||||||
|
# Select the Go version to target. The default is '1.13'.
|
||||||
|
go: "1.20.7"
|
||||||
|
checks: ["all"]
|
||||||
|
|
||||||
|
unparam:
|
||||||
|
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||||
|
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||||
|
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||||
|
# with golangci-lint call it on a directory with the changed file.
|
||||||
|
check-exported: false
|
||||||
|
|
||||||
|
unused:
|
||||||
|
# Select the Go version to target. The default is '1.13'.
|
||||||
|
go: "1.18.5"
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
FROM golang:alpine
|
||||||
|
|
||||||
|
# Set destination for COPY
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Download Go modules
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/tesla
|
||||||
|
|
||||||
|
# Run
|
||||||
|
CMD ["/app/tesla"]
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Tesla Stock Sdk
|
||||||
|
|
||||||
|
[![Build Status](https://drone.dev.m-and-m.ovh/api/badges/mderasse/teslastock-sdk/status.svg?ref=refs/heads/main)](https://drone.dev.m-and-m.ovh/mderasse/teslastock-sdk)
|
284
client.go
Normal file
284
client.go
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
package teslastocksdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPClient is an interface which declares the functionality we need from an
|
||||||
|
// HTTP client. This is to allow consumers to provide their own HTTP client as
|
||||||
|
// needed, without restricting them to only using *http.Client.
|
||||||
|
type HTTPClient interface {
|
||||||
|
Do(*http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client wraps http client.
|
||||||
|
type Client struct {
|
||||||
|
debugFlag *uint64
|
||||||
|
lastRequest *atomic.Value
|
||||||
|
lastResponse *atomic.Value
|
||||||
|
|
||||||
|
HTTPClient HTTPClient
|
||||||
|
userAgent string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient create a new tesla-stock client with default HTTP Client.
|
||||||
|
func NewClient(options ...ClientOptions) *Client {
|
||||||
|
|
||||||
|
client := Client{
|
||||||
|
debugFlag: new(uint64),
|
||||||
|
lastRequest: &atomic.Value{},
|
||||||
|
lastResponse: &atomic.Value{},
|
||||||
|
HTTPClient: http.DefaultClient,
|
||||||
|
userAgent: defaultUserAgent,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(&client)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDebugFlag sets the DebugFlag of the client, which are just bit flags that
|
||||||
|
// tell the client how to behave. They can be bitwise-ORed together to enable
|
||||||
|
// multiple behaviors.
|
||||||
|
func (c *Client) SetDebugFlag(flag DebugFlag) {
|
||||||
|
atomic.StoreUint64(c.debugFlag, uint64(flag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) debugCaptureRequest() bool {
|
||||||
|
return atomic.LoadUint64(c.debugFlag)&uint64(DEBUG_FLAG_CAPTURE_LAST_REQUEST) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) debugCaptureResponse() bool {
|
||||||
|
return atomic.LoadUint64(c.debugFlag)&uint64(DEBUG_FLAG_CAPTURE_LAST_RESPONSE) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastAPIRequest returns the last request sent to the API, if enabled. This can
|
||||||
|
// be turned on by using the SetDebugFlag() method while providing the
|
||||||
|
// DebugCaptureLastRequest flag.
|
||||||
|
//
|
||||||
|
// The bool value returned from this method is false if the request is unset or
|
||||||
|
// is nil. If there was an error prepping the request to be sent to the server,
|
||||||
|
// there will be no *http.Request to capture so this will return (<nil>, false).
|
||||||
|
//
|
||||||
|
// This is meant to help with debugging unexpected API interactions, so most
|
||||||
|
// won't need to use it. Also, callers will need to ensure the *Client isn't
|
||||||
|
// used concurrently, otherwise they might receive another method's *http.Request
|
||||||
|
// and not the one they anticipated.
|
||||||
|
//
|
||||||
|
// The *http.Request made within the Do() method is not captured by the client,
|
||||||
|
// and thus won't be returned by this method.
|
||||||
|
func (c *Client) LastAPIRequest() (*http.Request, bool) {
|
||||||
|
v := c.lastRequest.Load()
|
||||||
|
if v == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// comma ok idiom omitted, if this is something else explode
|
||||||
|
if r, ok := v.(*http.Request); ok && r != nil {
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastAPIResponse returns the last response received from the API, if enabled.
|
||||||
|
// This can be turned on by using the SetDebugFlag() method while providing the
|
||||||
|
// DebugCaptureLastResponse flag.
|
||||||
|
//
|
||||||
|
// The bool value returned from this method is false if the response is unset or
|
||||||
|
// is nil. If the HTTP exchange failed (e.g., there was a connection error)
|
||||||
|
// there will be no *http.Response to capture so this will return (<nil>,
|
||||||
|
// false).
|
||||||
|
//
|
||||||
|
// This is meant to help with debugging unexpected API interactions, so most
|
||||||
|
// won't need to use it. Also, callers will need to ensure the *Client isn't
|
||||||
|
// used concurrently, otherwise they might receive another method's *http.Response
|
||||||
|
// and not the one they anticipated.
|
||||||
|
//
|
||||||
|
// The *http.Response from the Do() method is not captured by the client, and thus
|
||||||
|
// won't be returned by this method.
|
||||||
|
func (c *Client) LastAPIResponse() (*http.Response, bool) {
|
||||||
|
v := c.lastResponse.Load()
|
||||||
|
if v == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// comma ok idiom omitted, if this is something else explode
|
||||||
|
if r, ok := v.(*http.Response); ok && r != nil {
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) delete(ctx context.Context, path string, payload interface{}, headers map[string]string) (*http.Response, error) {
|
||||||
|
if payload != nil {
|
||||||
|
data, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.do(ctx, http.MethodDelete, path, bytes.NewBuffer(data), headers)
|
||||||
|
}
|
||||||
|
return c.do(ctx, http.MethodDelete, path, nil, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) put(ctx context.Context, path string, payload interface{}, headers map[string]string) (*http.Response, error) {
|
||||||
|
if payload != nil {
|
||||||
|
data, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.do(ctx, http.MethodPut, path, bytes.NewBuffer(data), headers)
|
||||||
|
}
|
||||||
|
return c.do(ctx, http.MethodPut, path, nil, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) post(ctx context.Context, path string, payload interface{}, headers map[string]string) (*http.Response, error) {
|
||||||
|
data, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.do(ctx, http.MethodPost, path, bytes.NewBuffer(data), headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) get(ctx context.Context, path string, headers map[string]string) (*http.Response, error) {
|
||||||
|
return c.do(ctx, http.MethodGet, path, nil, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dupeRequest(r *http.Request) (*http.Request, error) {
|
||||||
|
dreq := r.Clone(r.Context())
|
||||||
|
|
||||||
|
if r.Body != nil {
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to copy request body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = r.Body.Close()
|
||||||
|
|
||||||
|
r.Body = io.NopCloser(bytes.NewReader(data))
|
||||||
|
dreq.Body = io.NopCloser(bytes.NewReader(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return dreq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) do(ctx context.Context, method, path string, body io.Reader, headers map[string]string) (*http.Response, error) {
|
||||||
|
var dreq *http.Request
|
||||||
|
var resp *http.Response
|
||||||
|
|
||||||
|
// so that the last request and response can be nil if there was an error
|
||||||
|
// before the request could be fully processed by the origin, we defer these
|
||||||
|
// calls here
|
||||||
|
if c.debugCaptureResponse() {
|
||||||
|
defer func() {
|
||||||
|
c.lastResponse.Store(resp)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.debugCaptureRequest() {
|
||||||
|
defer func() {
|
||||||
|
c.lastRequest.Store(dreq)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, stockApiEndpoint+path, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.prepareRequest(req, headers)
|
||||||
|
|
||||||
|
// if in debug mode, copy request before making it
|
||||||
|
if c.debugCaptureRequest() {
|
||||||
|
if dreq, err = dupeRequest(req); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to duplicate request for debug capture: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = c.HTTPClient.Do(req)
|
||||||
|
|
||||||
|
return c.checkResponse(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) prepareRequest(req *http.Request, headers map[string]string) {
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", c.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) checkResponse(resp *http.Response, err error) (*http.Response, error) {
|
||||||
|
if err != nil {
|
||||||
|
return resp, fmt.Errorf("error calling the API endpoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stock API always return a 200
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return resp, c.getErrorFromResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getErrorFromResponse(resp *http.Response) APIError {
|
||||||
|
// check whether the error response is declared as JSON
|
||||||
|
if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
|
||||||
|
defer func() {
|
||||||
|
if err := resp.Body.Close(); err != nil {
|
||||||
|
fmt.Printf("Error closing body: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
aerr := APIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
Message: fmt.Sprintf("HTTP response with status code %d does not contain Content-Type: application/json", resp.StatusCode),
|
||||||
|
}
|
||||||
|
|
||||||
|
return aerr
|
||||||
|
}
|
||||||
|
|
||||||
|
var document APIError
|
||||||
|
|
||||||
|
// because of above check this probably won't fail, but it's possible...
|
||||||
|
if err := c.decodeJSON(resp, &document); err != nil {
|
||||||
|
aerr := APIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
Message: fmt.Sprintf("HTTP response with status code %d, JSON error object decode failed: %s", resp.StatusCode, err),
|
||||||
|
}
|
||||||
|
|
||||||
|
return aerr
|
||||||
|
}
|
||||||
|
|
||||||
|
document.StatusCode = resp.StatusCode
|
||||||
|
|
||||||
|
return document
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) decodeJSON(resp *http.Response, payload interface{}) error {
|
||||||
|
// close the original response body, and not the copy we may make if
|
||||||
|
// debugCaptureResponse is true
|
||||||
|
orb := resp.Body
|
||||||
|
defer func() { _ = orb.Close() }() // explicitly discard error
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.debugCaptureResponse() { // reset body as we capture the response elsewhere
|
||||||
|
resp.Body = io.NopCloser(bytes.NewReader(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(body, payload)
|
||||||
|
}
|
11
client_options.go
Normal file
11
client_options.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package teslastocksdk
|
||||||
|
|
||||||
|
// ClientOptions allows for options to be passed into the Client for customization.
|
||||||
|
type ClientOptions func(*Client)
|
||||||
|
|
||||||
|
// WithUserAgent will force the usage of a specific user agent.
|
||||||
|
func WithUserAgent(userAgent string) ClientOptions {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.userAgent = userAgent
|
||||||
|
}
|
||||||
|
}
|
33
constant.go
Normal file
33
constant.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package teslastocksdk
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Version is the current version of the sdk.
|
||||||
|
Version = "1.0.0"
|
||||||
|
stockApiEndpoint = ""
|
||||||
|
defaultUserAgent = "TeslaApp/" + Version
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugFlag represents a set of debug bit flags that can be bitwise-ORed
|
||||||
|
// together to configure the different behaviors. This allows us to expand
|
||||||
|
// functionality in the future without introducing breaking changes.
|
||||||
|
type DebugFlag uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DEBUG_FLAG_DISABLED disables all debug behaviors.
|
||||||
|
DEBUG_FLAG_DISABLED DebugFlag = 0
|
||||||
|
|
||||||
|
// DEBUG_FLAG_CAPTURE_LAST_REQUEST captures the last HTTP request made to the API
|
||||||
|
// (if there was one) and makes it available via the LastAPIRequest()
|
||||||
|
// method.
|
||||||
|
//
|
||||||
|
// This may increase memory usage / GC, as we'll be making a copy of the
|
||||||
|
// full HTTP request body on each request and capturing it for inspection.
|
||||||
|
DEBUG_FLAG_CAPTURE_LAST_REQUEST DebugFlag = 1 << 0
|
||||||
|
|
||||||
|
// DEBUG_FLAG_CAPTURE_LAST_RESPONSE captures the last HTTP response from the API (if
|
||||||
|
// there was one) and makes it available via the LastAPIResponse() method.
|
||||||
|
//
|
||||||
|
// This may increase memory usage / GC, as we'll be making a copy of the
|
||||||
|
// full HTTP response body on each request and capturing it for inspection.
|
||||||
|
DEBUG_FLAG_CAPTURE_LAST_RESPONSE DebugFlag = 1 << 1
|
||||||
|
)
|
25
error.go
Normal file
25
error.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package teslastocksdk
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// APIError represent an API error.
|
||||||
|
type APIError struct {
|
||||||
|
StatusCode int `json:"-"`
|
||||||
|
Message string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error satisfy the error interface.
|
||||||
|
func (a APIError) Error() string {
|
||||||
|
if a.Message != "" {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"HTTP response failed with status code %d, error message: %s",
|
||||||
|
a.StatusCode,
|
||||||
|
a.Message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"HTTP response failed with status code %d and no error message",
|
||||||
|
a.StatusCode,
|
||||||
|
)
|
||||||
|
}
|
3
go.mod
Normal file
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module git.dev.m-and-m.ovh/mderasse/teslastock-sdk
|
||||||
|
|
||||||
|
go 1.19
|
38
stock.go
Normal file
38
stock.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package teslastocksdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetAvailabilities return the car availabilities matching with the provided characteristics.
|
||||||
|
func (c *Client) GetAvailabilities(ctx context.Context, params AvailabilityParams) (*AvailabilitiesResponse, error) {
|
||||||
|
b, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to marshal availability params. Error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := url.Values{
|
||||||
|
"query": {string(b)},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.get(
|
||||||
|
ctx,
|
||||||
|
fmt.Sprintf("/inventory/api/v1/inventory-results?%s", queryParams.Encode()),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = resp.Body.Close() }() // explicitly discard error
|
||||||
|
|
||||||
|
availabilities := AvailabilitiesResponse{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&availabilities); err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal response. Error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &availabilities, nil
|
||||||
|
}
|
434
stock_struct.go
Normal file
434
stock_struct.go
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
package teslastocksdk
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// modelEnum.
|
||||||
|
type modelEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
MODEL_3 modelEnum = "m3"
|
||||||
|
MODEL_Y modelEnum = "my"
|
||||||
|
MODEL_S modelEnum = "ms"
|
||||||
|
MODEL_X modelEnum = "mx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// conditionEnum.
|
||||||
|
type conditionEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CONDITION_NEW conditionEnum = "new"
|
||||||
|
CONDITION_USED conditionEnum = "used"
|
||||||
|
)
|
||||||
|
|
||||||
|
// arrangeByEnum allow to order availabilities by a specific element.
|
||||||
|
type arrangeByEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ARRANGE_BY_PRICE arrangeByEnum = "Price"
|
||||||
|
ARRANGE_BY_ODOMETER arrangeByEnum = "Odometer"
|
||||||
|
ARRANGE_BY_YEAR arrangeByEnum = "Year"
|
||||||
|
ARRANGE_BY_DISTANCE arrangeByEnum = "Distance"
|
||||||
|
)
|
||||||
|
|
||||||
|
// orderByEnum.
|
||||||
|
type orderByEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ORDER_BY_ASC orderByEnum = "asc"
|
||||||
|
ORDER_BY_DESC orderByEnum = "desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
/ OPTIONS ENUM
|
||||||
|
**/
|
||||||
|
|
||||||
|
// autopilotEnum.
|
||||||
|
type autopilotEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AUTOPILOT_AP autopilotEnum = "AUTOPILOT"
|
||||||
|
AUTOPILOT_ENHANCED autopilotEnum = "ENHANCED_AUTOPILOT"
|
||||||
|
AUTOPILOT_FSD autopilotEnum = "AUTOPILOT_FULL_SELF_DRIVING"
|
||||||
|
AUTOPILOT_ORIGINAL autopilotEnum = "AUTOPILOT_ORIGINAL"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cabinConfigEnum.
|
||||||
|
type cabinConfigEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CABIN_CONFIG_FIVE cabinConfigEnum = "FIVE"
|
||||||
|
CABIN_CONFIG_SIX cabinConfigEnum = "SIX"
|
||||||
|
CABIN_CONFIG_SEVEN cabinConfigEnum = "SEVEN"
|
||||||
|
)
|
||||||
|
|
||||||
|
// interiorEnum.
|
||||||
|
type interiorEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
INTERIOR_CREAM interiorEnum = "CREAM"
|
||||||
|
INTERIOR_WHITE interiorEnum = "WHITE"
|
||||||
|
INTERIOR_BLACK interiorEnum = "BLACK"
|
||||||
|
)
|
||||||
|
|
||||||
|
// paintEnum.
|
||||||
|
type paintEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PAINT_RED paintEnum = "RED"
|
||||||
|
PAINT_WHITE paintEnum = "WHITE"
|
||||||
|
PAINT_BLACK paintEnum = "BLACK"
|
||||||
|
PAINT_GREY paintEnum = "GREY"
|
||||||
|
PAINT_BLUE paintEnum = "BLUE"
|
||||||
|
PAINT_SILVER paintEnum = "SILVER"
|
||||||
|
)
|
||||||
|
|
||||||
|
// steeringWheelEnum.
|
||||||
|
type steeringWheelEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
STEERING_WHEEL_YOKE steeringWheelEnum = "STEERING_YOKE"
|
||||||
|
STEERING_WHEEL_ROUND steeringWheelEnum = "STEERING_ROUND"
|
||||||
|
)
|
||||||
|
|
||||||
|
// trimEnum.
|
||||||
|
type trimEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TRIM_MS_AWD trimEnum = "MSAWD"
|
||||||
|
TRIM_MS_PLAID trimEnum = "MSPLAID"
|
||||||
|
TRIM_M3_RWD trimEnum = "M3RWD"
|
||||||
|
TRIM_LR_AWD trimEnum = "LRAWD"
|
||||||
|
TRIM_PERF_AWD trimEnum = "PAWD"
|
||||||
|
TRIM_MX_PLAID trimEnum = "MXPLAID"
|
||||||
|
TRIM_MX_PERF trimEnum = "MXPERF"
|
||||||
|
TRIM_MX_AWD trimEnum = "MXAWD"
|
||||||
|
TRIM_MX_90D trimEnum = "90D"
|
||||||
|
TRIM_MY_RWD trimEnum = "MYRWD"
|
||||||
|
)
|
||||||
|
|
||||||
|
// wheelsEnum.
|
||||||
|
type wheelsEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
WHEELS_18 wheelsEnum = "EIGHTEEN"
|
||||||
|
WHEELS_19 wheelsEnum = "NINETEEN"
|
||||||
|
WHEELS_20 wheelsEnum = "TWENTY"
|
||||||
|
WHEELS_21 wheelsEnum = "TWENTY_ONE"
|
||||||
|
WHEELS_22 wheelsEnum = "TWENTY_TWO"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AvailabilityParams is the params accepted by the API.
|
||||||
|
type AvailabilityParams struct {
|
||||||
|
Query AvailabilityQueryParams `json:"query"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
OutsideOffset int `json:"outsideOffset"`
|
||||||
|
OutsideSearch bool `json:"outsideSearch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvailabilityQueryParams are the params to filter results.
|
||||||
|
type AvailabilityQueryParams struct {
|
||||||
|
Arrangeby arrangeByEnum `json:"arrangeby"`
|
||||||
|
Condition conditionEnum `json:"condition"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
Lat float64 `json:"lat"`
|
||||||
|
Lng float64 `json:"lng"`
|
||||||
|
Market string `json:"market"`
|
||||||
|
Model modelEnum `json:"model"`
|
||||||
|
Options OptionsParams `json:"options"`
|
||||||
|
Order orderByEnum `json:"order"`
|
||||||
|
Range int `json:"range"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
SuperRegion string `json:"super_region"`
|
||||||
|
Zip string `json:"zip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionsParams contain the car option.
|
||||||
|
type OptionsParams struct {
|
||||||
|
AdditionalOptions []string `json:"ADL_OPTS"`
|
||||||
|
Autopilot []autopilotEnum `json:"AUTOPILOT"`
|
||||||
|
CabinConfig []cabinConfigEnum `json:"CABIN_CONFIG"`
|
||||||
|
Interior []interiorEnum `json:"INTERIOR"`
|
||||||
|
Paint []paintEnum `json:"PAINT"`
|
||||||
|
SteeringWheel []steeringWheelEnum `json:"STEERING_WHEEL"`
|
||||||
|
Trim []trimEnum `json:"TRIM"`
|
||||||
|
Wheels []wheelsEnum `json:"WHEELS"`
|
||||||
|
Year []string `json:"Year"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
/ Response.
|
||||||
|
**/
|
||||||
|
|
||||||
|
// Availability represent a available car with its characteristics.
|
||||||
|
type Availability struct {
|
||||||
|
InTransit bool `json:"InTransit,omitempty"`
|
||||||
|
AdlOpts any `json:"ADL_OPTS,omitempty"`
|
||||||
|
Autopilot []string `json:"AUTOPILOT,omitempty"`
|
||||||
|
AcquisitionSubType any `json:"AcquisitionSubType,omitempty"`
|
||||||
|
AcquisitionType any `json:"AcquisitionType,omitempty"`
|
||||||
|
ActualGAInDate string `json:"ActualGAInDate,omitempty"`
|
||||||
|
Battery any `json:"BATTERY,omitempty"`
|
||||||
|
CabinConfig []string `json:"CABIN_CONFIG,omitempty"`
|
||||||
|
CPORefurbishmentStatus any `json:"CPORefurbishmentStatus,omitempty"`
|
||||||
|
City string `json:"City,omitempty"`
|
||||||
|
CompositorViews struct {
|
||||||
|
FrontView string `json:"frontView,omitempty"`
|
||||||
|
SideView string `json:"sideView,omitempty"`
|
||||||
|
InteriorView string `json:"interiorView,omitempty"`
|
||||||
|
} `json:"CompositorViews,omitempty"`
|
||||||
|
CountryCode string `json:"CountryCode,omitempty"`
|
||||||
|
CountryCodes []string `json:"CountryCodes,omitempty"`
|
||||||
|
CountryHasVehicleAtLocation bool `json:"CountryHasVehicleAtLocation,omitempty"`
|
||||||
|
CountryOfOrigin string `json:"CountryOfOrigin,omitempty"`
|
||||||
|
CurrencyCode string `json:"CurrencyCode,omitempty"`
|
||||||
|
CurrencyCodes string `json:"CurrencyCodes,omitempty"`
|
||||||
|
Decor any `json:"DECOR,omitempty"`
|
||||||
|
Drive []string `json:"DRIVE,omitempty"`
|
||||||
|
DamageDisclosureStatus any `json:"DamageDisclosureStatus,omitempty"`
|
||||||
|
DestinationHandlingFee int `json:"DestinationHandlingFee,omitempty"`
|
||||||
|
Discount int `json:"Discount,omitempty"`
|
||||||
|
DisplayWarranty bool `json:"DisplayWarranty,omitempty"`
|
||||||
|
EtaToCurrent string `json:"EtaToCurrent,omitempty"`
|
||||||
|
FactoryCode string `json:"FactoryCode,omitempty"`
|
||||||
|
FactoryDepartureDate string `json:"FactoryDepartureDate,omitempty"`
|
||||||
|
FixedAssets bool `json:"FixedAssets,omitempty"`
|
||||||
|
FlexibleOptionsData []struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
LongName string `json:"long_name,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Price int `json:"price,omitempty"`
|
||||||
|
} `json:"FlexibleOptionsData,omitempty"`
|
||||||
|
ForecastedFactoryGatedDate any `json:"ForecastedFactoryGatedDate,omitempty"`
|
||||||
|
Headliner any `json:"HEADLINER,omitempty"`
|
||||||
|
HasDamagePhotos bool `json:"HasDamagePhotos,omitempty"`
|
||||||
|
HasOptionCodeData bool `json:"HasOptionCodeData,omitempty"`
|
||||||
|
Hash string `json:"Hash,omitempty"`
|
||||||
|
Interior []string `json:"INTERIOR,omitempty"`
|
||||||
|
IncentivesDetails struct {
|
||||||
|
Current struct {
|
||||||
|
Fuel struct {
|
||||||
|
Data []struct {
|
||||||
|
Algorithm bool `json:"algorithm,omitempty"`
|
||||||
|
Amount any `json:"amount,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
IncentiveType string `json:"incentiveType,omitempty"`
|
||||||
|
Market string `json:"market,omitempty"`
|
||||||
|
Period string `json:"period,omitempty"`
|
||||||
|
Variables struct {
|
||||||
|
Distance any `json:"distance,omitempty"`
|
||||||
|
FuelEfficiencyImperial any `json:"fuel_efficiency_imperial,omitempty"`
|
||||||
|
FuelEfficiencyMetric float64 `json:"fuel_efficiency_metric,omitempty"`
|
||||||
|
FuelPrice float64 `json:"fuel_price,omitempty"`
|
||||||
|
KwhConsumption float64 `json:"kwh_consumption,omitempty"`
|
||||||
|
KwhPrice float64 `json:"kwh_price,omitempty"`
|
||||||
|
Months int `json:"months,omitempty"`
|
||||||
|
TollSavings int `json:"toll_savings,omitempty"`
|
||||||
|
} `json:"variables,omitempty"`
|
||||||
|
Variant string `json:"variant,omitempty"`
|
||||||
|
} `json:"data,omitempty"`
|
||||||
|
Total int `json:"total,omitempty"`
|
||||||
|
} `json:"fuel,omitempty"`
|
||||||
|
} `json:"current,omitempty"`
|
||||||
|
Total struct {
|
||||||
|
Fuel any `json:"fuel,omitempty"`
|
||||||
|
IncludedInPurchasePrice int `json:"includedInPurchasePrice,omitempty"`
|
||||||
|
Monthly int `json:"monthly,omitempty"`
|
||||||
|
Once int `json:"once,omitempty"`
|
||||||
|
} `json:"total,omitempty"`
|
||||||
|
} `json:"IncentivesDetails,omitempty"`
|
||||||
|
InspectionDocumentGUID any `json:"InspectionDocumentGuid,omitempty"`
|
||||||
|
InventoryPrice int `json:"InventoryPrice,omitempty"`
|
||||||
|
IsAtLocation bool `json:"IsAtLocation,omitempty"`
|
||||||
|
IsChargingConnectorIncluded bool `json:"IsChargingConnectorIncluded,omitempty"`
|
||||||
|
IsDemo bool `json:"IsDemo,omitempty"`
|
||||||
|
IsFactoryGated bool `json:"IsFactoryGated,omitempty"`
|
||||||
|
IsInTransit bool `json:"IsInTransit,omitempty"`
|
||||||
|
IsLegacy bool `json:"IsLegacy,omitempty"`
|
||||||
|
IsPreProdWithDisclaimer bool `json:"IsPreProdWithDisclaimer,omitempty"`
|
||||||
|
IsTegra bool `json:"IsTegra,omitempty"`
|
||||||
|
Language string `json:"Language,omitempty"`
|
||||||
|
Languages []string `json:"Languages,omitempty"`
|
||||||
|
LexiconDefaultOptions []struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
LongName string `json:"long_name,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
} `json:"LexiconDefaultOptions,omitempty"`
|
||||||
|
ListingType string `json:"ListingType,omitempty"`
|
||||||
|
ListingTypes string `json:"ListingTypes,omitempty"`
|
||||||
|
MarketingInUseDate any `json:"MarketingInUseDate,omitempty"`
|
||||||
|
Model string `json:"Model,omitempty"`
|
||||||
|
Odometer int `json:"Odometer,omitempty"`
|
||||||
|
OdometerType string `json:"OdometerType,omitempty"`
|
||||||
|
OnConfiguratorPricePercentage int `json:"OnConfiguratorPricePercentage,omitempty"`
|
||||||
|
OptionCodeData []struct {
|
||||||
|
AccelerationUnitLong string `json:"acceleration_unit_long,omitempty"`
|
||||||
|
AccelerationUnitShort string `json:"acceleration_unit_short,omitempty"`
|
||||||
|
AccelerationValue string `json:"acceleration_value,omitempty"`
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
Price int `json:"price,omitempty"`
|
||||||
|
UnitLong string `json:"unit_long,omitempty"`
|
||||||
|
UnitShort string `json:"unit_short,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
TopSpeedLabel string `json:"top_speed_label,omitempty"`
|
||||||
|
RangeLabelSource string `json:"range_label_source,omitempty"`
|
||||||
|
RangeSource string `json:"range_source,omitempty"`
|
||||||
|
RangeSourceInventoryNew string `json:"range_source_inventory_new,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
LongName string `json:"long_name,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
} `json:"OptionCodeData,omitempty"`
|
||||||
|
OptionCodeList string `json:"OptionCodeList,omitempty"`
|
||||||
|
OptionCodeListDisplayOnly any `json:"OptionCodeListDisplayOnly,omitempty"`
|
||||||
|
OptionCodePricing []struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
Price int `json:"price,omitempty"`
|
||||||
|
} `json:"OptionCodePricing,omitempty"`
|
||||||
|
OrderFee struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Value int `json:"value,omitempty"`
|
||||||
|
} `json:"OrderFee,omitempty"`
|
||||||
|
OriginalDeliveryDate any `json:"OriginalDeliveryDate,omitempty"`
|
||||||
|
OriginalInCustomerGarageDate any `json:"OriginalInCustomerGarageDate,omitempty"`
|
||||||
|
Paint []string `json:"PAINT,omitempty"`
|
||||||
|
PlannedGADailyDate string `json:"PlannedGADailyDate,omitempty"`
|
||||||
|
Price int64 `json:"Price,omitempty"`
|
||||||
|
PurchasePrice int `json:"PurchasePrice,omitempty"`
|
||||||
|
Roof any `json:"ROOF,omitempty"`
|
||||||
|
RegistrationCount int `json:"RegistrationCount,omitempty"`
|
||||||
|
SteeringWheel any `json:"STEERING_WHEEL,omitempty"`
|
||||||
|
SalesMetro string `json:"SalesMetro,omitempty"`
|
||||||
|
StateProvince string `json:"StateProvince,omitempty"`
|
||||||
|
StateProvinceLongName string `json:"StateProvinceLongName,omitempty"`
|
||||||
|
Trim []string `json:"TRIM,omitempty"`
|
||||||
|
TaxScheme any `json:"TaxScheme,omitempty"`
|
||||||
|
ThirdPartyHistoryURL any `json:"ThirdPartyHistoryUrl,omitempty"`
|
||||||
|
TitleStatus string `json:"TitleStatus,omitempty"`
|
||||||
|
TitleSubtype []string `json:"TitleSubtype,omitempty"`
|
||||||
|
TotalPrice int `json:"TotalPrice,omitempty"`
|
||||||
|
TradeInType any `json:"TradeInType,omitempty"`
|
||||||
|
TransportFees struct {
|
||||||
|
ExemptVRL []any `json:"exemptVRL,omitempty"`
|
||||||
|
Fees []any `json:"fees,omitempty"`
|
||||||
|
MetroFees []any `json:"metro_fees,omitempty"`
|
||||||
|
UnfundedLocationFees []any `json:"unfunded_location_fees,omitempty"`
|
||||||
|
} `json:"TransportFees,omitempty"`
|
||||||
|
TrimCode string `json:"TrimCode,omitempty"`
|
||||||
|
TrimName string `json:"TrimName,omitempty"`
|
||||||
|
Trt int `json:"Trt,omitempty"`
|
||||||
|
TrtName string `json:"TrtName,omitempty"`
|
||||||
|
Vin string `json:"VIN,omitempty"`
|
||||||
|
VehicleHistory any `json:"VehicleHistory,omitempty"`
|
||||||
|
VehicleRegion string `json:"VehicleRegion,omitempty"`
|
||||||
|
VrlName string `json:"VrlName,omitempty"`
|
||||||
|
Wheels []string `json:"WHEELS,omitempty"`
|
||||||
|
WarrantyBatteryExpDate time.Time `json:"WarrantyBatteryExpDate,omitempty"`
|
||||||
|
WarrantyBatteryIsExpired bool `json:"WarrantyBatteryIsExpired,omitempty"`
|
||||||
|
WarrantyBatteryMile int `json:"WarrantyBatteryMile,omitempty"`
|
||||||
|
WarrantyBatteryYear int `json:"WarrantyBatteryYear,omitempty"`
|
||||||
|
WarrantyData struct {
|
||||||
|
UsedVehicleLimitedWarrantyMile int `json:"UsedVehicleLimitedWarrantyMile,omitempty"`
|
||||||
|
UsedVehicleLimitedWarrantyYear int `json:"UsedVehicleLimitedWarrantyYear,omitempty"`
|
||||||
|
WarrantyBatteryExpDate time.Time `json:"WarrantyBatteryExpDate,omitempty"`
|
||||||
|
WarrantyBatteryIsExpired bool `json:"WarrantyBatteryIsExpired,omitempty"`
|
||||||
|
WarrantyBatteryMile int `json:"WarrantyBatteryMile,omitempty"`
|
||||||
|
WarrantyBatteryYear int `json:"WarrantyBatteryYear,omitempty"`
|
||||||
|
WarrantyDriveUnitExpDate time.Time `json:"WarrantyDriveUnitExpDate,omitempty"`
|
||||||
|
WarrantyDriveUnitMile int `json:"WarrantyDriveUnitMile,omitempty"`
|
||||||
|
WarrantyDriveUnitYear int `json:"WarrantyDriveUnitYear,omitempty"`
|
||||||
|
WarrantyMile int `json:"WarrantyMile,omitempty"`
|
||||||
|
WarrantyVehicleExpDate time.Time `json:"WarrantyVehicleExpDate,omitempty"`
|
||||||
|
WarrantyVehicleIsExpired bool `json:"WarrantyVehicleIsExpired,omitempty"`
|
||||||
|
WarrantyYear int `json:"WarrantyYear,omitempty"`
|
||||||
|
} `json:"WarrantyData,omitempty"`
|
||||||
|
WarrantyDriveUnitExpDate time.Time `json:"WarrantyDriveUnitExpDate,omitempty"`
|
||||||
|
WarrantyDriveUnitMile int `json:"WarrantyDriveUnitMile,omitempty"`
|
||||||
|
WarrantyDriveUnitYear int `json:"WarrantyDriveUnitYear,omitempty"`
|
||||||
|
WarrantyMile int `json:"WarrantyMile,omitempty"`
|
||||||
|
WarrantyVehicleExpDate time.Time `json:"WarrantyVehicleExpDate,omitempty"`
|
||||||
|
WarrantyVehicleIsExpired bool `json:"WarrantyVehicleIsExpired,omitempty"`
|
||||||
|
WarrantyYear int `json:"WarrantyYear,omitempty"`
|
||||||
|
Year int `json:"Year,omitempty"`
|
||||||
|
AlternateCurrency []any `json:"AlternateCurrency,omitempty"`
|
||||||
|
UsedVehicleLimitedWarrantyMile int `json:"UsedVehicleLimitedWarrantyMile,omitempty"`
|
||||||
|
UsedVehicleLimitedWarrantyYear int `json:"UsedVehicleLimitedWarrantyYear,omitempty"`
|
||||||
|
OdometerTypeShort string `json:"OdometerTypeShort,omitempty"`
|
||||||
|
DeliveryDateDisplay bool `json:"DeliveryDateDisplay,omitempty"`
|
||||||
|
TransportationFee int `json:"TransportationFee,omitempty"`
|
||||||
|
VrlList []struct {
|
||||||
|
Vrl int `json:"vrl,omitempty"`
|
||||||
|
Lat int `json:"lat,omitempty"`
|
||||||
|
Lon int `json:"lon,omitempty"`
|
||||||
|
VrlLocks []any `json:"vrlLocks,omitempty"`
|
||||||
|
} `json:"vrlList,omitempty"`
|
||||||
|
OptionCodeSpecs struct {
|
||||||
|
CSpecs struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Options []struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
LongName string `json:"long_name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
} `json:"options,omitempty"`
|
||||||
|
} `json:"C_SPECS,omitempty"`
|
||||||
|
CDesign struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Options []any `json:"options,omitempty"`
|
||||||
|
} `json:"C_DESIGN,omitempty"`
|
||||||
|
CCallouts struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Options []struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
LongName string `json:"long_name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
List []string `json:"list,omitempty"`
|
||||||
|
Period string `json:"period,omitempty"`
|
||||||
|
} `json:"options,omitempty"`
|
||||||
|
} `json:"C_CALLOUTS,omitempty"`
|
||||||
|
COpts struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Options []struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
LongName string `json:"long_name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
} `json:"options,omitempty"`
|
||||||
|
} `json:"C_OPTS,omitempty"`
|
||||||
|
} `json:"OptionCodeSpecs,omitempty"`
|
||||||
|
CompositorViewsCustom struct {
|
||||||
|
IsProductWithCustomViews bool `json:"isProductWithCustomViews,omitempty"`
|
||||||
|
ExternalZoom struct {
|
||||||
|
Order int `json:"order,omitempty"`
|
||||||
|
Search int `json:"search,omitempty"`
|
||||||
|
} `json:"externalZoom,omitempty"`
|
||||||
|
ExternalCrop struct {
|
||||||
|
Order string `json:"order,omitempty"`
|
||||||
|
Search string `json:"search,omitempty"`
|
||||||
|
} `json:"externalCrop,omitempty"`
|
||||||
|
} `json:"CompositorViewsCustom,omitempty"`
|
||||||
|
IsRangeStandard bool `json:"IsRangeStandard,omitempty"`
|
||||||
|
MetroName string `json:"MetroName,omitempty"`
|
||||||
|
GeoPoints [][]any `json:"geoPoints,omitempty"`
|
||||||
|
HasMarketingOptions bool `json:"HasMarketingOptions,omitempty"`
|
||||||
|
InTransitMetroName string `json:"InTransitMetroName,omitempty"`
|
||||||
|
InTransitSalesMetro string `json:"InTransitSalesMetro,omitempty"`
|
||||||
|
FirstRegistrationDate any `json:"FirstRegistrationDate,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvailabilitiesResponse contain the a list of car availability.
|
||||||
|
type AvailabilitiesResponse struct {
|
||||||
|
Results []Availability `json:"results,omitempty"`
|
||||||
|
TotalMatchesFound string `json:"total_matches_found,omitempty"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user