-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This bootstraps a new repository for the `oapi-codegen` organisation's standards, and then implements the `Nullable` type as per [0] and [1]. Using a `map` as the underlying type allows us to take advantage of `json.Marshal`'s inbuilt checks to determine whether to `omitempty` a JSON value, which isn't possible with a `struct`. We can make sure this is a multi-module project, similar to other projects, so we can isolate test-only dependencies from the core project, which has zero dependencies. We can also add convenience helpers for `NewNullableWithValue` and `NewNullNullable` as they can be useful when constructing `struct`s in tests. In the top-level project we can use runnable examples to indicate the example usage and cover all the test cases we need, and then use the `internal/test` package to perform further checks. Co-authored-by: Sebastien Guilloux <sebastien.guilloux@elastic.co> Co-authored-by: Ashutosh Kumar <ashutosh.kumar@elastic.co> [0]: golang/go#64515 (comment) [1]: https://github.com/sebgl/nullable/
- Loading branch information
0 parents
commit d2fce25
Showing
16 changed files
with
813 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* @oapi-codegen/maintainers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
_extends: .github |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Build project | ||
on: [ push, pull_request ] | ||
jobs: | ||
build: | ||
name: Build | ||
runs-on: ubuntu-latest | ||
strategy: | ||
fail-fast: false | ||
# perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go | ||
matrix: | ||
version: | ||
- "1.20" | ||
- "1.21" | ||
steps: | ||
- name: Check out source code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: ${{ matrix.version }} | ||
|
||
- name: Test | ||
run: make test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Lint project | ||
on: [push, pull_request] | ||
jobs: | ||
build: | ||
name: Build | ||
runs-on: ubuntu-latest | ||
strategy: | ||
fail-fast: false | ||
# perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go | ||
matrix: | ||
version: | ||
- "1.20" | ||
- "1.21" | ||
steps: | ||
- name: Check out source code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: ${{ matrix.version }} | ||
|
||
- name: Run `make lint-ci` | ||
run: make lint-ci |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: Release Drafter | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
workflow_dispatch: {} | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
update_release_draft: | ||
permissions: | ||
contents: write | ||
pull-requests: write | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: release-drafter/release-drafter@v5 | ||
with: | ||
name: next | ||
tag: next | ||
version: next | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
name: Ensure `go mod tidy` has been run | ||
on: [ push, pull_request ] | ||
jobs: | ||
build: | ||
name: Build | ||
runs-on: ubuntu-latest | ||
strategy: | ||
fail-fast: false | ||
# perform matrix testing to give us an earlier insight into issues with different versions of supported major versions of Go | ||
matrix: | ||
version: | ||
- "1.20" | ||
- "1.21" | ||
steps: | ||
- name: Check out source code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: ${{ matrix.version }} | ||
|
||
- name: Install `tidied` | ||
run: go install gitlab.com/jamietanna/tidied@latest | ||
|
||
- name: Check for no untracked files | ||
run: tidied -verbose |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/bin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright 2024 oapi-codegen | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
GOBASE=$(shell pwd) | ||
GOBIN=$(GOBASE)/bin | ||
|
||
help: | ||
@echo "This is a helper makefile for oapi-codegen" | ||
@echo "Targets:" | ||
@echo " test: run all tests" | ||
@echo " tidy tidy go mod" | ||
@echo " lint run linting" | ||
|
||
$(GOBIN)/golangci-lint: | ||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.55.2 | ||
|
||
.PHONY: tools | ||
tools: $(GOBIN)/golangci-lint | ||
|
||
lint: tools | ||
git ls-files go.mod '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && $(GOBIN)/golangci-lint run ./...' | ||
|
||
lint-ci: tools | ||
git ls-files go.mod '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && $(GOBIN)/golangci-lint run ./... --out-format=github-actions --timeout=5m' | ||
|
||
test: | ||
git ls-files go.mod '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && go test -cover ./...' | ||
|
||
tidy: | ||
@echo "tidy..." | ||
git ls-files go.mod '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && go mod tidy' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# oapi-codegen/nullable | ||
|
||
> An implementation of a `Nullable` type for JSON bodies, indicating whether the field is absent, set to null, or set to a value | ||
Unlike other known implementations, this makes it possible to both marshal and unmarshal the value, as well as represent all three states: | ||
|
||
- the field is _not set_ | ||
- the field is _explicitly set to null_ | ||
- the field is _explicitly set to a given value_ | ||
|
||
And can be embedded in structs, for instance with the following definition: | ||
|
||
```go | ||
obj := struct { | ||
// RequiredID is a required, nullable field | ||
RequiredID nullable.Nullable[int] `json:"id"` | ||
// RequiredID is an optional, nullable field | ||
OptionalString *nullable.Nullable[string] `json:"optionalString,omitempty"` | ||
}{} | ||
``` | ||
|
||
## Usage | ||
|
||
> [!IMPORTANT] | ||
> Although this project is under the [oapi-codegen org](https://github.com/oapi-codegen) for the `oapi-codegen` OpenAPI-to-Go code generator, this is intentionally released as a separate, standalone library which can be used by other projects. | ||
First, add to your project with: | ||
|
||
```sh | ||
go get github.com/oapi-codegen/nullable | ||
``` | ||
|
||
Check out the examples in [the package documentation on pkg.go.dev](https://pkg.go.dev/github.com/oapi-codegen/nullable) for more details. | ||
|
||
## Credits | ||
|
||
- [KumanekoSakura](https://github.com/KumanekoSakura), [via](https://github.com/golang/go/issues/64515#issuecomment-1841057182) | ||
- [Sebastien Guilloux], [via](https://github.com/sebgl/nullable/) | ||
|
||
As well as contributions from: | ||
|
||
- [Jamie Tanna](https://www.jvt.me) | ||
- [Ashutosh Kumar](https://github.com/sonasingh46) | ||
|
||
## License | ||
|
||
Licensed under the Apache-2.0 license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/oapi-codegen/nullable | ||
|
||
go 1.20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
module github.com/oapi-codegen/nullable/internal/test | ||
|
||
go 1.20 | ||
|
||
replace github.com/oapi-codegen/nullable => ../../ | ||
|
||
require ( | ||
github.com/oapi-codegen/nullable v0.0.0-00010101000000-000000000000 | ||
github.com/stretchr/testify v1.8.4 | ||
) | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package nullable_test | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
|
||
"github.com/oapi-codegen/nullable" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type Obj struct { | ||
Foo nullable.Nullable[string] `json:"foo,omitempty"` // note "omitempty" is important for fields that are optional | ||
} | ||
|
||
func TestNullable(t *testing.T) { | ||
// --- parsing from json and serializing back to JSON | ||
|
||
// -- case where there is an actual value | ||
data := `{"foo":"bar"}` | ||
// deserialize from json | ||
myObj := parse(data, t) | ||
require.Equal(t, myObj, Obj{Foo: nullable.Nullable[string]{true: "bar"}}) | ||
require.False(t, myObj.Foo.IsNull()) | ||
require.True(t, myObj.Foo.IsSpecified()) | ||
value, err := myObj.Foo.Get() | ||
require.NoError(t, err) | ||
require.Equal(t, "bar", value) | ||
// serialize back to json: leads to the same data | ||
require.Equal(t, data, serialize(myObj, t)) | ||
|
||
// -- case where no value is specified: parsed from JSON | ||
data = `{}` | ||
// deserialize from json | ||
myObj = parse(data, t) | ||
require.Equal(t, myObj, Obj{Foo: nil}) | ||
require.False(t, myObj.Foo.IsNull()) | ||
require.False(t, myObj.Foo.IsSpecified()) | ||
_, err = myObj.Foo.Get() | ||
require.ErrorContains(t, err, "value is not specified") | ||
// serialize back to json: leads to the same data | ||
require.Equal(t, data, serialize(myObj, t)) | ||
|
||
// -- case where the specified value is explicitly null | ||
data = `{"foo":null}` | ||
// deserialize from json | ||
myObj = parse(data, t) | ||
require.Equal(t, myObj, Obj{Foo: nullable.Nullable[string]{false: ""}}) | ||
require.True(t, myObj.Foo.IsNull()) | ||
require.True(t, myObj.Foo.IsSpecified()) | ||
_, err = myObj.Foo.Get() | ||
require.ErrorContains(t, err, "value is null") | ||
// serialize back to json: leads to the same data | ||
require.Equal(t, data, serialize(myObj, t)) | ||
|
||
// --- building objects from a Go client | ||
|
||
// - case where there is an actual value | ||
myObj = Obj{} | ||
myObj.Foo.Set("bar") | ||
require.Equal(t, `{"foo":"bar"}`, serialize(myObj, t)) | ||
|
||
// - case where the value should be unspecified | ||
myObj = Obj{} | ||
// do nothing: unspecified by default | ||
require.Equal(t, `{}`, serialize(myObj, t)) | ||
// explicitly mark unspecified | ||
myObj.Foo.SetUnspecified() | ||
require.Equal(t, `{}`, serialize(myObj, t)) | ||
|
||
// - case where the value should be null | ||
myObj = Obj{} | ||
myObj.Foo.SetNull() | ||
require.Equal(t, `{"foo":null}`, serialize(myObj, t)) | ||
} | ||
|
||
func parse(data string, t *testing.T) Obj { | ||
var myObj Obj | ||
err := json.Unmarshal([]byte(data), &myObj) | ||
require.NoError(t, err) | ||
return myObj | ||
} | ||
|
||
func serialize(o Obj, t *testing.T) string { | ||
data, err := json.Marshal(o) | ||
require.NoError(t, err) | ||
return string(data) | ||
} |
Oops, something went wrong.