diff --git a/.github/workflows/nix.yaml b/.github/workflows/nix.yaml new file mode 100644 index 00000000..75cbc7c4 --- /dev/null +++ b/.github/workflows/nix.yaml @@ -0,0 +1,61 @@ +--- +name: nix +on: + push: + pull_request: +permissions: + contents: read + pull-requests: read +concurrency: + group: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }} + cancel-in-progress: true +jobs: + test-go-versions: + strategy: + fail-fast: false + matrix: + go-version: ['124', '125'] + os: + - system: x86_64-linux + runs-on: ubuntu-24.04 + - system: aarch64-darwin + runs-on: macos-14 + runs-on: ${{ matrix.os.runs-on }} + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + accept-flake-config = true + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: devenv + - name: Build with Go 1.${{ matrix.go-version }} on ${{ matrix.os.system }} + run: nix build .#packages.${{ matrix.os.system }}.go-jsonschema-go${{ matrix.go-version }} --print-build-logs + - name: Test with Go 1.${{ matrix.go-version }} on ${{ matrix.os.system }} + run: nix build .#checks.${{ matrix.os.system }}.tests-go${{ matrix.go-version }} --print-build-logs + - name: Upload coverage to Codecov + if: matrix.go-version == '125' && matrix.os.system == 'x86_64-linux' + uses: codecov/codecov-action@v5 + with: + files: ./result/coverage.out + fail_ci_if_error: false + qa: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + accept-flake-config = true + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: devenv + - name: Run Nix flake checks + run: nix flake check --print-build-logs --show-trace diff --git a/.gitignore b/.gitignore index 16047034..7ba8f63f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ mise.toml.tmp dist/ output/ vendor/ +.direnv +result +.pre-commit-config.yaml +*.log diff --git a/.goreleaser.yaml b/.goreleaser.yaml index c66bf73b..b9abe35c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -24,18 +24,18 @@ archives: checksum: name_template: checksums.txt snapshot: - version_template: "{{ incpatch .Version }}-next" + version_template: '{{ incpatch .Version }}-next' changelog: sort: asc filters: exclude: - - "^docs:" - - "^test:" + - '^docs:' + - '^test:' release: github: owner: omissis name: go-jsonschema - name_template: "{{ .Tag }}" + name_template: '{{ .Tag }}' prerelease: auto brews: - name: go-jsonschema diff --git a/.rules/yamllint.yaml b/.rules/yamllint.yaml index f5a0b8d5..f84a7d6b 100644 --- a/.rules/yamllint.yaml +++ b/.rules/yamllint.yaml @@ -12,3 +12,4 @@ ignore: | **/node_modules/** **/helm_chart/** **/.github/** + .pre-commit-config.yaml diff --git a/README.md b/README.md index 35ea622e..d16aca60 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ along with unmarshalling code that validates the input JSON according to the sch ## Installing -* **Download**: Get a release [here](https://github.com/atombender/go-jsonschema/releases). +* **Download**: Get a release from the + [releases page](https://github.com/atombender/go-jsonschema/releases). * **Install from source**: To install with Go 1.23+: @@ -42,6 +43,40 @@ This project makes use of [go workspaces](https://go.dev/ref/mod#workspaces) in generated code during development while keeping the codebase as tidy and maintainable as possible. It's an unusual choice, but it allows to not only test the code-generation logic, but also the generated code itself. +## Nix Development Environment + +This project uses [Nix](https://nixos.org/) for reproducible development environments and CI/CD. +Nix solves dependency management across different systems, ensuring consistent builds and tests regardless of your +local setup. + +### Quick Start + +```shell +# Enter development shell with all tools +nix develop + +# Run all checks (tests, lints, formatting) +nix flake check + +# Run specific checks +nix build .#checks.x86_64-linux.tests-go125 # Integration tests +nix build .#checks.x86_64-linux.lint-golang # Go linting +nix build .#checks.x86_64-linux.build-goreleaser # GoReleaser build + +# Format code +nix fmt + +# Test CI workflows locally +nix run .#test-ci +``` + +### Why Nix? + +- **Reproducible builds**: Same dependencies across all environments (dev, CI, production) +- **Hermetic testing**: Tests run in isolated sandboxes with no external network access +- **Multi-version support**: Test against Go 1.24 and 1.25 simultaneously +- **No Docker required**: Native support for NixOS and other Linux distributions + ## Usage At its most basic: @@ -76,9 +111,10 @@ Note the flag format: ### Regenerating tests' golden files -It sometimes happen that new features or bug fixes to the library require regenerating the tests' golden files, here's how to do it: +It sometimes happen that new features or bug fixes to the library require +regenerating the tests' golden files, here's how to do it: -``` +```shell export OVERWRITE_EXPECTED_GO_FILE="true" make test ``` diff --git a/codecov.yml b/codecov.yml index ad746881..2debf1be 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,36 +1,34 @@ ignore: - - "**/*.json" - - "**/*.md" - - "**/*.mod" - - "**/*.sum" - - "**/*.yaml" - - "**/*.yml" - - "**/*_test.go" - - "**/Dockerfile" - - "**/LICENSE" - - "**/Makefile" - - ".github/" - - ".rules/" - - ".vscode/" - - "coverage/" - - "dist/" - - "docs/" - - "output/" - - "scripts/" - + - '**/*.json' + - '**/*.md' + - '**/*.mod' + - '**/*.sum' + - '**/*.yaml' + - '**/*.yml' + - '**/*_test.go' + - '**/Dockerfile' + - '**/LICENSE' + - '**/Makefile' + - .github/ + - .rules/ + - .vscode/ + - coverage/ + - dist/ + - docs/ + - output/ + - scripts/ codecov: require_ci_to_pass: true branch: main - coverage: status: project: app: target: auto - paths: "!tests/" + paths: '!tests/' tests: target: auto - paths: "tests/" + paths: tests/ patch: enabled: true target: auto diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..59e23bdf --- /dev/null +++ b/flake.lock @@ -0,0 +1,142 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1762440070, + "narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762441963, + "narHash": "sha256-j+rNQ119ffYUkYt2YYS6rnd6Jh/crMZmbqpkGLXaEt0=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "8e7576e79b88c16d7ee3bbd112c8d90070832885", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1762363567, + "narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1761765539, + "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762410071, + "narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "97a30861b13c3731a84e09405414398fbf3e109f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..d9f9b942 --- /dev/null +++ b/flake.nix @@ -0,0 +1,386 @@ +{ + description = "go-jsonschema - Generate Go types from JSON Schema"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + treefmt-nix.url = "github:numtide/treefmt-nix"; + treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; + git-hooks.url = "github:cachix/git-hooks.nix"; + git-hooks.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = inputs @ {flake-parts, ...}: + flake-parts.lib.mkFlake {inherit inputs;} { + imports = [ + inputs.treefmt-nix.flakeModule + inputs.git-hooks.flakeModule + ]; + + systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; + + perSystem = { + config, + pkgs, + ... + }: let + cleanSrc = pkgs.lib.cleanSourceWith { + src = ./.; + filter = path: type: + !(pkgs.lib.hasSuffix "go.work" path) + && !(pkgs.lib.hasSuffix "go.work.sum" path); + }; + + makePackage = goPackage: let + buildGoModule = pkgs.buildGoModule.override {go = goPackage;}; + in + buildGoModule { + pname = "go-jsonschema"; + version = "0.0.0-dev"; + src = cleanSrc; + vendorHash = "sha256-CBxxloy9W9uJq4l2zUrp6VJlu5lNCX55ks8OOWkHDF4="; + subPackages = ["."]; + ldflags = [ + "-s" + "-w" + ]; + meta = with pkgs.lib; { + description = "Generate Go types from JSON Schema"; + homepage = "https://github.com/atombender/go-jsonschema"; + license = licenses.mit; + maintainers = []; + }; + }; + + makeTests = goPackage: let + buildGoModule = pkgs.buildGoModule.override {go = goPackage;}; + + # Tests for main module + mainTests = buildGoModule { + name = "go-jsonschema-tests-main"; + src = cleanSrc; + vendorHash = "sha256-CBxxloy9W9uJq4l2zUrp6VJlu5lNCX55ks8OOWkHDF4="; + buildPhase = '' + export HOME=$TMPDIR + mkdir -p coverage/pkg + echo "Running tests in root module with coverage..." + go test -v -race -covermode=atomic -coverpkg=./... -cover ./... -args -test.gocoverdir="$PWD/coverage/pkg" + ''; + installPhase = '' + mkdir -p $out + cp -r coverage/pkg $out/ || true + echo "Main module tests passed" > $out/result + ''; + }; + + # Tests for tests module (integration tests) + integrationTests = buildGoModule { + name = "go-jsonschema-tests-integration"; + src = cleanSrc; + vendorHash = "sha256-VCSDBMTWCz2KTPEOotBtNTBDDqhDSEE+zDvxX7X9a0s="; + modRoot = "tests"; + buildPhase = '' + export HOME=$TMPDIR + mkdir -p coverage/tests + echo "Running integration tests with coverage..." + go test -v -race -covermode=atomic -coverpkg=./... -cover ./... -args -test.gocoverdir="$PWD/coverage/tests" + ''; + installPhase = '' + mkdir -p $out + cp -r coverage/tests $out/ || true + echo "Integration tests passed" > $out/result + ''; + }; + in + pkgs.runCommand "go-jsonschema-tests" { + buildInputs = [mainTests integrationTests]; + } '' + mkdir -p $out + echo "All tests passed successfully" > $out/test-results + ''; + in { + packages = { + go-jsonschema-go124 = makePackage pkgs.go_1_24; + go-jsonschema-go125 = makePackage pkgs.go; + default = makePackage pkgs.go; + + test-ci = pkgs.writeShellApplication { + name = "test-ci"; + runtimeInputs = [pkgs.act]; + text = '' + exec act -W .github/workflows/nix.yaml -P ubuntu-24.04=catthehacker/ubuntu:act-24.04 "$@" + ''; + }; + }; + + checks = { + tests-go124 = makeTests pkgs.go_1_24; + tests-go125 = makeTests pkgs.go; + + lint-golang = let + buildGoModule = pkgs.buildGoModule.override {go = pkgs.go;}; + in + buildGoModule { + name = "lint-golang"; + src = cleanSrc; + vendorHash = "sha256-CBxxloy9W9uJq4l2zUrp6VJlu5lNCX55ks8OOWkHDF4="; + nativeBuildInputs = [pkgs.golangci-lint]; + buildPhase = '' + export HOME=$TMPDIR + golangci-lint -v run --modules-download-mode vendor --color=always --config=.rules/.golangci.yml ./... --skip-dirs tests + ''; + installPhase = '' + mkdir -p $out + echo "Go linting passed" > $out/result + ''; + }; + + lint-dockerfile = pkgs.stdenv.mkDerivation { + name = "lint-dockerfile"; + src = ./.; + + nativeBuildInputs = [pkgs.hadolint]; + + buildPhase = '' + find . \ + -type f \ + -name '*Dockerfile*' \ + -not -path './.git/*' \ + -exec hadolint {} \; + ''; + + installPhase = '' + mkdir -p $out + echo "Dockerfile linting passed" > $out/result + ''; + }; + + lint-json = pkgs.stdenv.mkDerivation { + name = "lint-json"; + src = ./.; + + nativeBuildInputs = [pkgs.nodePackages.jsonlint]; + + buildPhase = '' + find . \ + -type f \ + -not -path ".git" \ + -not -path ".github" \ + -not -path ".vscode" \ + -not -path ".idea" \ + -name "*.json" \ + -exec jsonlint -c -q -t ' ' {} \; + ''; + + installPhase = '' + mkdir -p $out + echo "JSON linting passed" > $out/result + ''; + }; + + lint-makefile = pkgs.stdenv.mkDerivation { + name = "lint-makefile"; + src = ./.; + + nativeBuildInputs = [pkgs.checkmake]; + + buildPhase = '' + checkmake --config .rules/checkmake.ini Makefile + ''; + + installPhase = '' + mkdir -p $out + echo "Makefile linting passed" > $out/result + ''; + }; + + lint-markdown = pkgs.stdenv.mkDerivation { + name = "lint-markdown"; + src = ./.; + + nativeBuildInputs = [pkgs.markdownlint-cli2]; + + buildPhase = '' + markdownlint-cli2 "**/*.md" --config ".rules/.markdownlint.yaml" + ''; + + installPhase = '' + mkdir -p $out + echo "Markdown linting passed" > $out/result + ''; + }; + + lint-shell = pkgs.stdenv.mkDerivation { + name = "lint-shell"; + src = ./.; + + nativeBuildInputs = [pkgs.shellcheck]; + + buildPhase = '' + shellcheck -a -o all -s bash -- **/*.sh + ''; + + installPhase = '' + mkdir -p $out + echo "Shell linting passed" > $out/result + ''; + }; + + lint-yaml = pkgs.stdenv.mkDerivation { + name = "lint-yaml"; + src = ./.; + + nativeBuildInputs = [pkgs.yamllint]; + + buildPhase = '' + yamllint -c .rules/yamllint.yaml . + ''; + + installPhase = '' + mkdir -p $out + echo "YAML linting passed" > $out/result + ''; + }; + + build-goreleaser = let + buildGoModule = pkgs.buildGoModule.override {go = pkgs.go;}; + in + buildGoModule { + name = "build-goreleaser"; + src = cleanSrc; + vendorHash = "sha256-CBxxloy9W9uJq4l2zUrp6VJlu5lNCX55ks8OOWkHDF4="; + nativeBuildInputs = [pkgs.goreleaser pkgs.git]; + + buildPhase = '' + export HOME=$TMPDIR + export GO_VERSION=$(go version | cut -d ' ' -f 3) + + goreleaser check + goreleaser release --skip=before,docker --verbose --snapshot --clean + ''; + + installPhase = '' + mkdir -p $out + cp -r dist $out/ || true + echo "GoReleaser build passed" > $out/result + ''; + }; + }; + + treefmt = { + projectRootFile = "flake.nix"; + + programs = { + alejandra.enable = true; + # Disabled to avoid modifying .go files in tests/data + # gofmt.enable = true; + # gofumpt.enable = true; + }; + + settings.global.excludes = [ + ".direnv/*" + "result" + ".git/*" + ]; + + settings.formatter = { + shfmt = { + command = "${pkgs.shfmt}/bin/shfmt"; + options = ["-i" "2" "-ci" "-sr" "-w"]; + includes = ["*.sh"]; + }; + + # Temporarily disabled - yq keeps rewriting files even without changes + # yq = { + # command = pkgs.writeShellApplication { + # name = "format-yaml"; + # runtimeInputs = [pkgs.yq-go]; + # text = '' + # for file in "$@"; do + # yq eval -P -I 2 -i "$file" + # done + # ''; + # }; + # includes = ["*.yaml" "*.yml"]; + # }; + + # Disabled to avoid modifying .json files in tests/data + # jq = { + # command = pkgs.writeShellApplication { + # name = "format-json"; + # text = '' + # for file in "$@"; do + # ${pkgs.jq}/bin/jq -M . "$file" > "$file.tmp" && mv "$file.tmp" "$file" + # done + # ''; + # }; + # includes = ["*.json"]; + # }; + + # Disabled to avoid modifying .go files in tests/data + # goimports = { + # command = "${pkgs.gotools}/bin/goimports"; + # options = ["-w" "-local" "github.com/atombender"]; + # includes = ["*.go"]; + # excludes = ["vendor/*" "tests/data/*"]; + # }; + + markdownlint-cli2 = { + command = "${pkgs.markdownlint-cli2}/bin/markdownlint-cli2"; + options = ["--config" ".rules/.markdownlint.yaml" "--fix"]; + includes = ["*.md"]; + excludes = [".direnv/*" "result/*"]; + }; + }; + }; + + pre-commit = { + check.enable = true; + settings.hooks = { + treefmt = { + enable = true; + package = config.treefmt.build.wrapper; + # Don't use --fail-on-change, just format the files + entry = "${config.treefmt.build.wrapper}/bin/treefmt"; + pass_filenames = false; + }; + }; + }; + + devShells.default = pkgs.mkShell { + packages = with pkgs; + [ + go + gopls + gotools + go-tools + golangci-lint + adr-tools + goreleaser + hadolint + markdownlint-cli2 + shellcheck + shfmt + yamllint + yq-go + nodePackages.jsonlint + checkmake + gofumpt + jq + config.treefmt.build.wrapper + act + config.pre-commit.settings.package + ] + ++ config.pre-commit.settings.enabledPackages; + + shellHook = '' + ${config.pre-commit.installationScript} + echo "go-jsonschema development environment" + echo "Go version: $(go version)" + ''; + }; + + formatter = config.treefmt.build.wrapper; + }; + }; +} diff --git a/main.go b/main.go index 00cbe233..4f3d4c2e 100644 --- a/main.go +++ b/main.go @@ -239,13 +239,13 @@ func allKeys(in ...map[string]string) []string { return result } -func logf(format string, args ...interface{}) { +func logf(format string, args ...any) { fmt.Fprint(os.Stderr, "go-jsonschema: ") fmt.Fprintf(os.Stderr, format, args...) fmt.Fprint(os.Stderr, "\n") } -func verboseLogf(format string, args ...interface{}) { +func verboseLogf(format string, args ...any) { if verbose { logf(format, args...) } diff --git a/pkg/codegen/emitter.go b/pkg/codegen/emitter.go index b25fca1d..379ace10 100644 --- a/pkg/codegen/emitter.go +++ b/pkg/codegen/emitter.go @@ -39,10 +39,7 @@ func (e *Emitter) Indent(n int32) { func (e *Emitter) Comment(s string) { if s != "" { - limit := e.maxLineLength - e.indent - if limit < 0 { - limit = 0 - } + limit := max(e.maxLineLength-e.indent, 0) //nolint:gosec // limit is guarded against negative values lines := strings.Split(wordwrap.WrapString(s, uint(limit)), "\n") @@ -53,13 +50,10 @@ func (e *Emitter) Comment(s string) { } } -func (e *Emitter) Commentf(s string, args ...interface{}) { +func (e *Emitter) Commentf(s string, args ...any) { s = fmt.Sprintf(s, args...) if s != "" { - limit := e.maxLineLength - e.indent - if limit < 0 { - limit = 0 - } + limit := max(e.maxLineLength-e.indent, 0) //nolint:gosec // limit is guarded against negative values lines := strings.Split(wordwrap.WrapString(s, uint(limit)), "\n") @@ -70,13 +64,13 @@ func (e *Emitter) Commentf(s string, args ...interface{}) { } } -func (e *Emitter) Printf(format string, args ...interface{}) { +func (e *Emitter) Printf(format string, args ...any) { e.checkIndent() fmt.Fprintf(&e.sb, format, args...) e.start = false } -func (e *Emitter) Printlnf(format string, args ...interface{}) { +func (e *Emitter) Printlnf(format string, args ...any) { e.Printf(format, args...) e.Newline() } diff --git a/pkg/codegen/model.go b/pkg/codegen/model.go index ae56bffb..e64fb818 100644 --- a/pkg/codegen/model.go +++ b/pkg/codegen/model.go @@ -146,7 +146,7 @@ func (p *Package) Generate(out *Emitter) error { type Var struct { Type Type Name string - Value interface{} + Value any } func (v *Var) GetName() string { @@ -171,7 +171,7 @@ func (v *Var) Generate(out *Emitter) error { type Constant struct { Type Type Name string - Value interface{} + Value any } func (c *Constant) GetName() string { @@ -427,7 +427,7 @@ func (NullType) Generate(out *Emitter) error { type StructType struct { Fields []StructField RequiredJSONFields []string - DefaultValue interface{} + DefaultValue any } func (*StructType) IsNillable() bool { return false } @@ -468,7 +468,7 @@ type StructField struct { Comment string Tags string JSONName string - DefaultValue interface{} + DefaultValue any SchemaType *schemas.Type } diff --git a/pkg/generator/schema_generator.go b/pkg/generator/schema_generator.go index db5dc923..b74ed6ea 100644 --- a/pkg/generator/schema_generator.go +++ b/pkg/generator/schema_generator.go @@ -3,6 +3,7 @@ package generator import ( "errors" "fmt" + "slices" "strings" "github.com/google/go-cmp/cmp" @@ -812,19 +813,19 @@ func (g *schemaGenerator) addStructField( SchemaType: prop, } - tags := "" + var tags strings.Builder if isRequired || g.DisableOmitempty() { for _, tag := range g.config.Tags { - tags += fmt.Sprintf(`%s:"%s" `, tag, name) + tags.WriteString(fmt.Sprintf(`%s:"%s" `, tag, name)) } } else { for _, tag := range g.config.Tags { - tags += fmt.Sprintf(`%s:"%s,omitempty" `, tag, name) + tags.WriteString(fmt.Sprintf(`%s:"%s,omitempty" `, tag, name)) } } - structField.Tags = strings.TrimSpace(tags) + structField.Tags = strings.TrimSpace(tags.String()) if structField.Comment == "" { structField.Comment = fmt.Sprintf("%s corresponds to the JSON schema field %q.", @@ -1350,10 +1351,8 @@ func (g *schemaGenerator) isTypeNullable(t *schemas.Type) (int, bool) { return 0, true } - for _, tt := range t.Type { - if tt == schemas.TypeNameNull { - return -1, true - } + if slices.Contains(t.Type, schemas.TypeNameNull) { + return -1, true } return -1, false diff --git a/pkg/generator/validator.go b/pkg/generator/validator.go index 68602358..4325d524 100644 --- a/pkg/generator/validator.go +++ b/pkg/generator/validator.go @@ -153,7 +153,7 @@ type defaultValidator struct { jsonName string fieldName string defaultValueType codegen.Type - defaultValue interface{} + defaultValue any } func (v *defaultValidator) generate(out *codegen.Emitter, format string) error { @@ -176,14 +176,14 @@ func (v *defaultValidator) dumpDefaultValueAssignment(out *codegen.Emitter) (any if nt, ok := v.defaultValueType.(*codegen.NamedType); ok { dvm, ok := v.defaultValue.(map[string]any) if ok { - namedFields := "" + var namedFields strings.Builder for _, k := range sortedKeys(dvm) { - namedFields += fmt.Sprintf("\n%s: %s,", upperFirst(k), litter.Sdump(dvm[k])) + namedFields.WriteString(fmt.Sprintf("\n%s: %s,", upperFirst(k), litter.Sdump(dvm[k]))) } - namedFields += "\n" + namedFields.WriteString("\n") - defaultValue := fmt.Sprintf(`%s{%s}`, nt.Decl.GetName(), namedFields) + defaultValue := fmt.Sprintf(`%s{%s}`, nt.Decl.GetName(), namedFields.String()) return fmt.Sprintf(`%s = %s`, getPlainName(v.fieldName), defaultValue), nil } @@ -246,7 +246,7 @@ func (v *defaultValidator) tryDumpDefaultSlice(maxLineLen int32) (string, error) kind := reflect.ValueOf(v.defaultValue).Kind() if kind == reflect.Slice { - df, ok := v.defaultValue.([]interface{}) + df, ok := v.defaultValue.([]any) if !ok { return "", ErrInvalidDefaultValue } diff --git a/pkg/schemas/model.go b/pkg/schemas/model.go index 2bb7860c..d4cba741 100644 --- a/pkg/schemas/model.go +++ b/pkg/schemas/model.go @@ -175,7 +175,7 @@ type Type struct { Properties map[string]*Type `json:"properties,omitempty"` // Section 5.16. PatternProperties map[string]*Type `json:"patternProperties,omitempty"` // Section 5.17. AdditionalProperties *Type `json:"additionalProperties,omitempty"` // Section 5.18. - Enum []interface{} `json:"enum,omitempty"` // Section 5.20. + Enum []any `json:"enum,omitempty"` // Section 5.20. Type TypeList `json:"type,omitempty"` // Section 5.21. // RFC draft-bhutton-json-schema-01, section 10. AllOf []*Type `json:"allOf,omitempty"` // Section 10.2.1.1. @@ -183,10 +183,10 @@ type Type struct { OneOf []*Type `json:"oneOf,omitempty"` // Section 10.2.1.3. Not *Type `json:"not,omitempty"` // Section 10.2.1.4. // RFC draft-wright-json-schema-validation-00, section 6, 7. - Title string `json:"title,omitempty"` // Section 6.1. - Description string `json:"description,omitempty"` // Section 6.1. - Default interface{} `json:"default,omitempty"` // Section 6.2. - Format string `json:"format,omitempty"` // Section 7. + Title string `json:"title,omitempty"` // Section 6.1. + Description string `json:"description,omitempty"` // Section 6.1. + Default any `json:"default,omitempty"` // Section 6.2. + Format string `json:"format,omitempty"` // Section 7. // RFC draft-wright-json-schema-hyperschema-00, section 4. Media *Type `json:"media,omitempty"` // Section 4.3. BinaryEncoding string `json:"binaryEncoding,omitempty"` // Section 4.3. @@ -393,7 +393,7 @@ func updateAllRefsValues(structValue *reflect.Value, refPath string) error { type typeListTransformer struct{} func (t typeListTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { - if typ == reflect.TypeOf(TypeList{}) { + if typ == reflect.TypeFor[TypeList]() { return func(dst, src reflect.Value) error { return nil } diff --git a/pkg/schemas/parse.go b/pkg/schemas/parse.go index ef1970fb..1c6a981c 100644 --- a/pkg/schemas/parse.go +++ b/pkg/schemas/parse.go @@ -48,7 +48,7 @@ func FromYAMLFile(fileName string) (*Schema, error) { func FromYAMLReader(r io.Reader) (*Schema, error) { // Marshal to JSON first because YAML decoder doesn't understand JSON tags. - var m map[string]interface{} + var m map[string]any if err := yaml.NewDecoder(r).Decode(&m); err != nil { return nil, fmt.Errorf("failed to unmarshal YAML: %w", err) diff --git a/pkg/schemas/types.go b/pkg/schemas/types.go index 99b4428b..15e5dd4b 100644 --- a/pkg/schemas/types.go +++ b/pkg/schemas/types.go @@ -24,8 +24,8 @@ func IsPrimitiveType(t string) bool { } func CleanNameForSorting(name string) string { - if strings.HasPrefix(name, PrefixEnumValue) { - return strings.TrimPrefix(name, PrefixEnumValue) + "_enumValues" // Append a string for sorting properly. + if after, found := strings.CutPrefix(name, PrefixEnumValue); found { + return after + "_enumValues" // Append a string for sorting properly. } return name diff --git a/pkg/yamlutils/yaml.go b/pkg/yamlutils/yaml.go index b121ee65..049cd13c 100644 --- a/pkg/yamlutils/yaml.go +++ b/pkg/yamlutils/yaml.go @@ -3,24 +3,24 @@ package yamlutils import "fmt" // FixMapKeys fixes non-string keys that occur in nested YAML unmarshalling results. -func FixMapKeys(m map[string]interface{}) { +func FixMapKeys(m map[string]any) { for k, v := range m { m[k] = fixMapKeysIn(v) } } // Fix non-string keys that occur in nested YAML unmarshalling results. -func fixMapKeysIn(value interface{}) interface{} { +func fixMapKeysIn(value any) any { switch t := value.(type) { - case []interface{}: + case []any: for i, elem := range t { t[i] = fixMapKeysIn(elem) } return t - case map[interface{}]interface{}: - m := map[string]interface{}{} + case map[any]any: + m := map[string]any{} for k, v := range t { ks, ok := k.(string) diff --git a/tests/data/extraImports/gopkgYAMLv3AdditionalProperties/gopkgYAMLv3AdditionalProperties.yaml b/tests/data/extraImports/gopkgYAMLv3AdditionalProperties/gopkgYAMLv3AdditionalProperties.yaml index 089ac90f..546ff26b 100644 --- a/tests/data/extraImports/gopkgYAMLv3AdditionalProperties/gopkgYAMLv3AdditionalProperties.yaml +++ b/tests/data/extraImports/gopkgYAMLv3AdditionalProperties/gopkgYAMLv3AdditionalProperties.yaml @@ -1,6 +1,6 @@ --- -foo: "example1" -bar: "example2" +foo: example1 +bar: example2 baz: - property1: "hello" + property1: hello property2: 123