Skip to content

Commit db107b0

Browse files
loretoCopilot
andauthored
Move optional and pointer to opensource, update lint rules (#480)
## Summary This PR does two simple changes: - It makes our linting rules very close to the ones in Axiom - It moves the code for the optional on-pointer packages from Axiom to open source. This is a straight copy of the code, with no functionality or logic changes ## How was it tested? Ran unit tests ## Community Contribution License All community contributions in this pull request are licensed to the project maintainers under the terms of the [Apache 2 License](https://www.apache.org/licenses/LICENSE-2.0). By creating this pull request I represent that I have the right to license the contributions to the project maintainers under the Apache 2 License as stated in the [Community Contribution License](https://github.com/jetify-com/opensource/blob/main/CONTRIBUTING.md#community-contribution-license). --------- Signed-off-by: Daniel Loreto <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent f28b84d commit db107b0

9 files changed

+1276
-15
lines changed

.golangci.yml

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
# TODO(Landau): Turn on all linters
2-
# TODO(Landau): move this to top level of opensource?
31
run:
42
go: "1.23"
53
linters:
64
disable-all: true
75
enable:
86
- dupl
9-
- errorlint
107
- errcheck
8+
- errorlint
119
- gofmt
1210
- goimports
1311
- gosimple
@@ -27,14 +25,34 @@ linters:
2725
- usestdlibvars
2826
- usetesting
2927
- varnamelen
30-
# If we're going to use github.com/pkg/errors we should probably turn this on?
31-
# Let's adopt new stackerr lib and we can turn this on.
3228
# - wrapcheck
33-
- unused
29+
issues:
30+
exclude-files:
31+
- ".*\\.connect\\.go$"
32+
- ".*\\.pb\\.go$"
33+
exclude:
34+
# It's usually better to start off _not_ wrapping an error unless it
35+
# should be part of your API. Errors should be wrapped only when it's
36+
# useful.
37+
#
38+
# See https://go.dev/blog/go1.13-errors
39+
- "non-wrapping format verb for fmt.Errorf"
40+
exclude-rules:
41+
- path: _test\.go
42+
linters:
43+
- dupl
3444

45+
# TODO(gcurtis): temporary until this file is used.
46+
- path: terminal.*\.go
47+
linters:
48+
- unused
3549
linters-settings:
36-
errorlint:
37-
errorf: false
50+
errcheck:
51+
exclude-functions:
52+
# Dropped/canceled/timed out connections or bad clients make checking this
53+
# error pointless 99% of the time.
54+
- (net/http.ResponseWriter).Write
55+
- (flag/*FlagSet).Parse
3856
revive:
3957
rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
4058
- name: atomic
@@ -56,15 +74,38 @@ linters-settings:
5674
- name: var-naming
5775
- name: unreachable-code
5876
varnamelen:
59-
max-distance: 10
77+
max-distance: 40
6078
ignore-decls:
61-
- id []byte
79+
- c echo.Context
80+
- const C
81+
- db database.Db
82+
- db db
83+
- e error
84+
- e watch.Event
85+
- f *foo.Bar
86+
- i int
87+
- id string
88+
- id any
89+
- m map[string]any
90+
- m map[string]int
91+
- mc api.CliServiceClient
92+
- ns *komponents.Namespace
93+
- ns komponents.Namespace
94+
- ns string
6295
- r *http.Request
63-
- r io.Reader
96+
- T any
6497
- t testing.T
65-
- v any
98+
- vc *viewer.MockViewer
99+
- vc Context
100+
- vc viewer
101+
- vc viewer.CommonContext
102+
- vc viewer.Context
66103
- w http.ResponseWriter
67104
- w io.Writer
68-
# TODO: enable this
69-
# unparam:
70-
# check-exported: true
105+
unconvert:
106+
fast-math: true
107+
wrapcheck:
108+
ignoreSigRegexps:
109+
- ".*confkit.*Validate.*"
110+
ignorePackageGlobs:
111+
- "go.jetify.com/axiom/api/*"

pkg/optional/doc.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Package optional provides a generic Option[T] type and helpers for representing
2+
// values that may or may not be present.
3+
//
4+
// The Option[T] type is a lightweight, allocation-free abstraction similar to the
5+
// "Maybe" or "Optional" types found in other languages. It is useful for
6+
// expressing that a value might be absent without resorting to pointers or
7+
// sentinel values.
8+
//
9+
// # Basic construction
10+
//
11+
// There are two canonical constructors:
12+
//
13+
// opt := optional.Some(42) // value is set
14+
// opt := optional.None[int]() // value is not set
15+
//
16+
// Accessing values
17+
//
18+
// v, ok := opt.Get() // returns value and whether it is set
19+
// v := opt.GetOrElse(0) // returns value or a default
20+
// v := opt.MustGet() // panics if value is not set
21+
// if opt.IsNone() { ... } // tests for absence
22+
//
23+
// Pointers
24+
//
25+
// opt := optional.Ptr(ptr) // wraps a *T, set to the value of ptr when ptr != nil
26+
//
27+
// # Primitive helpers
28+
//
29+
// For convenience the package exposes helper constructors for primitive types:
30+
//
31+
// optional.Int(1) // Option[int]
32+
// optional.Float64(3.14) // Option[float64]
33+
// optional.String("hi") // Option[string]
34+
//
35+
// These are entirely equivalent to calling Some(value) but sometimes improve
36+
// readability.
37+
//
38+
// # JSON integration
39+
//
40+
// Option implements json.Marshaler and json.Unmarshaler. A None value
41+
// marshals to JSON null, while Some(value) marshals to the JSON representation
42+
// of the contained value.
43+
//
44+
// When Option fields are used inside structs the 'omitzero' tag can be used to
45+
// omit None() values entirely:
46+
//
47+
// type Person struct {
48+
// Name string `json:"name"`
49+
// Age optional.Option[int] `json:"age,omitzero"` // omitted when None
50+
// }
51+
//
52+
// # Zero value behavior
53+
//
54+
// The zero value of Option[T] is considered "not set" (None). As a result an
55+
// Option field in a struct that is left uninitialized will behave as None().
56+
package optional

pkg/optional/optional.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package optional
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
// Option represents an optional value of type T
9+
type Option[T any] struct {
10+
value T
11+
set bool
12+
}
13+
14+
// Some creates a value that is set
15+
func Some[T any](value T) Option[T] {
16+
return Option[T]{
17+
value: value,
18+
set: true,
19+
}
20+
}
21+
22+
// None creates an empty value that is not set
23+
func None[T any]() Option[T] {
24+
return Option[T]{
25+
set: false,
26+
}
27+
}
28+
29+
// Ptr creates an Option value from a pointer.
30+
// The value is set if the pointer is not nil, and not set if the pointer is nil.
31+
//
32+
// T should not be a pointer type.
33+
func Ptr[T any](ptr *T) Option[T] {
34+
if ptr == nil {
35+
return None[T]()
36+
}
37+
return Some(*ptr)
38+
}
39+
40+
// Get returns the underlying value and whether it's set
41+
func (o Option[T]) Get() (T, bool) {
42+
return o.value, o.set
43+
}
44+
45+
// MustGet returns the value or panics if not set
46+
func (o Option[T]) MustGet() T {
47+
if !o.set {
48+
panic("option value not set")
49+
}
50+
return o.value
51+
}
52+
53+
// GetOrElse returns the contained value or a default if it is not set
54+
func (o Option[T]) GetOrElse(defaultValue T) T {
55+
if !o.set {
56+
return defaultValue
57+
}
58+
return o.value
59+
}
60+
61+
// IsNone returns true if there is no set value
62+
func (o Option[T]) IsNone() bool {
63+
return !o.set
64+
}
65+
66+
// String returns a textual representation for debugging/logging
67+
func (o Option[T]) String() string {
68+
if !o.set {
69+
return "None"
70+
}
71+
return fmt.Sprintf("Some(%v)", o.value)
72+
}
73+
74+
// GoString returns a Go-syntax representation
75+
func (o Option[T]) GoString() string {
76+
if !o.set {
77+
return fmt.Sprintf("None[%T]()", o.value)
78+
}
79+
return fmt.Sprintf("Some[%T](%#v)", o.value, o.value)
80+
}
81+
82+
// MarshalJSON implements json.Marshaler interface.
83+
// Some(value) marshals to the value's JSON representation
84+
// None() marshals to JSON null
85+
//
86+
// When used with struct fields:
87+
//
88+
// - None() appears as null by default
89+
//
90+
// - With 'omitzero' tag: None() values are omitted from JSON
91+
//
92+
// type Person struct {
93+
// Name string `json:"name"`
94+
// Age Option[int] `json:"age,omitzero"` // Omitted when None
95+
// Address Option[string] `json:"address"` // Appears as null when None
96+
// }
97+
func (o Option[T]) MarshalJSON() ([]byte, error) {
98+
if !o.set {
99+
return []byte("null"), nil
100+
}
101+
return json.Marshal(o.value)
102+
}
103+
104+
// UnmarshalJSON implements json.Unmarshaler interface.
105+
// JSON null becomes None(), other values become Some(value)
106+
func (o *Option[T]) UnmarshalJSON(data []byte) error {
107+
if string(data) == "null" {
108+
*o = None[T]()
109+
return nil
110+
}
111+
112+
var value T
113+
if err := json.Unmarshal(data, &value); err != nil {
114+
return err
115+
}
116+
117+
*o = Some(value)
118+
return nil
119+
}
120+
121+
// IsZero returns 'false' if the value is set, and 'true' if it is not set.
122+
// It makes Option compatible with the 'omitzero' JSON tag.
123+
func (o Option[T]) IsZero() bool {
124+
return !o.set
125+
}

0 commit comments

Comments
 (0)