diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml deleted file mode 100644 index 6bbdcf26..00000000 --- a/.github/workflows/build-and-test.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Go - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.17 - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v ./... diff --git a/.github/workflows/development.yaml b/.github/workflows/development.yaml new file mode 100644 index 00000000..29d3de16 --- /dev/null +++ b/.github/workflows/development.yaml @@ -0,0 +1,46 @@ +--- +name: development + +on: + push: + branches: + - main + - introduce-gh-actions + 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: + qa: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Golang + uses: actions/setup-go@v3 + with: + go-version: "^1.19.4" + - name: Lint Go files + uses: golangci/golangci-lint-action@v3 + with: + version: v1.52.2 + args: -v --color=always --config=.rules/.golangci.yml ./... + - name: Run tests + run: go test -v -race -covermode=atomic -coverprofile=coverage.out ./... + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + - name: Build binaries + uses: goreleaser/goreleaser-action@v3 + with: + distribution: goreleaser + version: "1.13.1" + args: release --debug --snapshot --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GO_VERSION: "1.19.4" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..9ab441db --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,65 @@ +--- +name: tag + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-beta[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-alpha[0-9]+" + +permissions: + contents: write + +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + release: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Fetch all tags + run: git fetch --force --tags + - name: Setup Golang + uses: actions/setup-go@v3 + with: + go-version: "^1.19.4" + - name: Lint Go files + uses: golangci/golangci-lint-action@v3 + with: + version: v1.52.2 + args: -v --color=always --config=.rules/.golangci.yml ./... + - name: Run tests + run: go test -v -race -covermode=atomic -coverprofile=coverage.out ./... + - name: Run GoReleaser for pre-release + if: ${{ contains(github.ref_name, '-') }} + uses: goreleaser/goreleaser-action@v3 + with: + distribution: goreleaser + version: "1.13.1" + args: release --debug --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GO_VERSION: "1.19.4" + - name: Log in to Docker Hub + if: ${{ !contains(github.ref_name, '-') }} + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Run GoReleaser for release + if: ${{ !contains(github.ref_name, '-') }} + uses: goreleaser/goreleaser-action@v3 + with: + distribution: goreleaser + version: "1.13.1" + args: release --debug --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GO_VERSION: "1.19.4" diff --git a/.gitignore b/.gitignore index a676215f..dd330f74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ +.env +.envrc .idea +.tool-versions bin +coverage.out diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 00000000..63958880 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,58 @@ +--- +project_name: go-jsonschema +before: + hooks: + - go mod tidy +builds: + - main: cmd/gojsonschema/main.go + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + ldflags: + - -s -w -X main.version={{.Version}} -X main.gitCommit={{.Commit}} -X main.buildTime={{.Date}} -X main.goVersion={{.Env.GO_VERSION}} -X main.osArch={{.Arch}} +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" +release: + github: + owner: omissis + name: go-jsonschema + name_template: "{{ .Tag }}" + prerelease: auto +brews: + - name: go-jsonschema + tap: + owner: omissis + name: homebrew-go-jsonschema + skip_upload: auto + folder: Formula + homepage: "https://gihub.com/omissis/go-jsonschema" + description: "go-jsonschema binary" + test: | + system "#{bin}/go-jsonschema" + install: | + bin.install 'go-jsonschema' +dockers: + - skip_push: auto + image_templates: + - "omissis/go-jsonschema:latest" + - "omissis/go-jsonschema:v{{ .Major }}" + - "omissis/go-jsonschema:v{{ .Major }}.{{ .Minor }}" + - "omissis/go-jsonschema:{{ .Tag }}" diff --git a/.rules/.golangci.yml b/.rules/.golangci.yml new file mode 100644 index 00000000..f24fc49c --- /dev/null +++ b/.rules/.golangci.yml @@ -0,0 +1,537 @@ +# This file contains all available configuration options +# with their default values (in comments). + +# Options for analysis running. +run: + timeout: 5m + skip-files: + - ".*\\.gen\\.go$" + modules-download-mode: readonly + +# output configuration options +output: + sort-results: true + +linters: + # Enable all available linters. + # Default: false + enable-all: true + # Disable specific linter + # https://golangci-lint.run/usage/linters/#disabled-by-default-linters--e--enable + disable: + # todo + - ireturn + - revive + - varnamelen + - godox + - funlen + - nestif + - gocognit + - cyclop + # unsupported + - rowserrcheck + - sqlclosecheck + - wastedassign + # deprecated + - deadcode + - exhaustivestruct + - golint + - nosnakecase + - structcheck + - ifshort + - interfacer + - maligned + - scopelint + - varcheck + # unused + - exhaustruct + - forbidigo + +linters-settings: + decorder: + disable-init-func-first-check: false + errcheck: + check-type-assertions: true + check-blank: true + disable-default-exclusions: false + exclude-functions: [] + errchkjson: + check-error-free-encoding: false + report-no-exported: true + exhaustive: + check-generated: true + default-signifies-exhaustive: true + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/omissis) # Custom section: groups all imports with the specified Prefix. + skip-generated: true + custom-order: true + godot: + scope: all + exclude: + - "^fixme:" + - "^todo:" + capital: true + period: true + gofumpt: + lang-version: "1.19" + extra-rules: true + goimports: + local-prefixes: github.com/omissis + govet: + check-shadowing: true + grouper: + const-require-single-const: true + const-require-grouping: false + import-require-single-import: true + import-require-grouping: false + var-require-single-var: true + var-require-grouping: false + nolintlint: + require-explanation: true + require-specific: true + revive: + enable-all-rules: true + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant + - name: add-constant + severity: warning + disabled: false + arguments: + - maxLitCount: "3" + allowStrs: '""' + allowInts: "0,1,2" + allowFloats: "0.0,0.,1.0,1.,2.0,2." + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#argument-limit + - name: argument-limit + severity: warning + disabled: false + arguments: [4] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic + - name: atomic + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#banned-characters + - name: banned-characters + severity: warning + disabled: false + arguments: ["Ω", "Σ", "σ", "7"] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bare-return + - name: bare-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports + - name: blank-imports + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr + - name: bool-literal-in-expr + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#call-to-gc + - name: call-to-gc + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cognitive-complexity + - name: cognitive-complexity + severity: warning + disabled: false + arguments: [7] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming + - name: confusing-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-results + - name: confusing-results + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr + - name: constant-logical-expr + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument + - name: context-as-argument + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-keys-type + - name: context-keys-type + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cyclomatic + - name: cyclomatic + severity: warning + disabled: false + arguments: [3] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#datarace + - name: datarace + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit + - name: deep-exit + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#defer + - name: defer + severity: warning + disabled: false + arguments: + - ["call-chain", "loop"] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports + - name: dot-imports + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#duplicated-imports + - name: duplicated-imports + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return + - name: early-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block + - name: empty-block + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines + - name: empty-lines + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming + - name: error-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-return + - name: error-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-strings + - name: error-strings + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#errorf + - name: errorf + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported + - name: exported + severity: warning + disabled: false + arguments: + - "checkPrivateReceivers" + - "sayRepetitiveInsteadOfStutters" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#file-header + - name: file-header + severity: warning + disabled: false + arguments: + - This is the text that must appear at the top of source files. + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter + - name: flag-parameter + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-result-limit + - name: function-result-limit + severity: warning + disabled: false + arguments: [2] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length + - name: function-length + severity: warning + disabled: false + arguments: [10, 0] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#get-return + - name: get-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#identical-branches + - name: identical-branches + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#if-return + - name: if-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#increment-decrement + - name: increment-decrement + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#indent-error-flow + - name: indent-error-flow + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#imports-blacklist + - name: imports-blacklist + severity: warning + disabled: false + arguments: + - "crypto/md5" + - "crypto/sha1" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing + - name: import-shadowing + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit + - name: line-length-limit + severity: warning + disabled: false + arguments: [120] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-public-structs + - name: max-public-structs + severity: warning + disabled: false + arguments: [3] + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-parameter + - name: modifies-parameter + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-value-receiver + - name: modifies-value-receiver + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#nested-structs + - name: nested-structs + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#optimize-operands-order + - name: optimize-operands-order + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments + - name: package-comments + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range + - name: range + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-in-closure + - name: range-val-in-closure + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-address + - name: range-val-address + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming + - name: receiver-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redefines-builtin-id + - name: redefines-builtin-id + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-of-int + - name: string-of-int + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format + - name: string-format + severity: warning + disabled: false + arguments: + - - 'core.WriteError[1].Message' + - '/^([^A-Z]|$)/' + - must not start with a capital letter + - - 'fmt.Errorf[0]' + - '/(^|[^\.!?])$/' + - must not end in punctuation + - - panic + - '/^[^\n]*$/' + - must not contain line breaks + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag + - name: struct-tag + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#superfluous-else + - name: superfluous-else + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-equal + - name: time-equal + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-naming + - name: time-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-naming + - name: var-naming + severity: warning + disabled: false + arguments: + - ["ID"] # AllowList + - ["VM"] # DenyList + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-declaration + - name: var-declaration + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unconditional-recursion + - name: unconditional-recursion + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming + - name: unexported-naming + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return + - name: unexported-return + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error + - name: unhandled-error + severity: warning + disabled: false + arguments: + - "fmt.Printf" + - "myFunction" + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unnecessary-stmt + - name: unnecessary-stmt + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unreachable-code + - name: unreachable-code + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter + - name: unused-parameter + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver + - name: unused-receiver + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break + - name: useless-break + severity: warning + disabled: false + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value + - name: waitgroup-by-value + severity: warning + disabled: false + varcheck: + exported-fields: true + varnamelen: + check-receiver: true + check-return: true + check-type-param: true + ignore-type-assert-ok: true + ignore-map-index-ok: true + ignore-chan-recv-ok: true + ignore-names: + - err + ignore-decls: + - c echo.Context + - t testing.T + - e error + - i int + - const C + - T any + - m map[string]int + wsl: + allow-cuddle-declarations: false + allow-multiline-assign: true + allow-separated-leading-comment: false + force-case-trailing-whitespace: 1 + force-err-cuddling: true +issues: + # List of regexps of issue texts to exclude. + # + # But independently of this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. + # To list all excluded by default patterns execute `golangci-lint run --help` + # + # Default: [] + exclude: [] + + # 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: + - gochecknoglobals + - cyclop + - dupl + - errcheck + - forcetypeassert + - funlen + - goconst + - gocyclo + - goerr113 + - gosec + - lll + - maintidx + - path: main\.go + linters: + - gochecknoglobals + + # Independently of option `exclude` we use default exclude patterns, + # it can be disabled by this option. + # To list all excluded by default patterns execute `golangci-lint run --help`. + # Default: true. + exclude-use-default: true + + # If set to true exclude and exclude-rules regular expressions become case-sensitive. + # Default: false + exclude-case-sensitive: false + + # The list of ids of default excludes to include or disable. + # Default: [] + include: [] + + # Maximum issues count per one linter. + # Set to 0 to disable. + # Default: 50 + max-issues-per-linter: 50 + + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 3 + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing large codebase. + # It's not practical to fix all existing issues at the moment of integration: + # much better don't allow issues in new code. + # + # Default: false. + new: false + + # Show only new issues created after git revision `REV`. + # new-from-rev: HEAD + + # Show only new issues created in git patch with set file path. + # new-from-patch: path/to/patch/file + + # Fix found issues (if it's supported by the linter). + fix: false + +severity: + # Set the default severity for issues. + # + # If severity rules are defined and the issues do not match or no severity is provided to the rule + # this will be the default severity applied. + # Severities should match the supported severity names of the selected out format. + # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity + # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity + # - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + # + # Default value is an empty string. + default-severity: error + + # If set to true `severity-rules` regular expressions become case-sensitive. + # Default: false + case-sensitive: true + + # When a list of severity rules are provided, severity information will be added to lint issues. + # Severity rules have the same filtering capability as exclude rules + # except you are allowed to specify one matcher per severity rule. + # Only affects out formats that support setting severity information. + # + # Default: [] + rules: + - linters: + - dupl + severity: info \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5923d288 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM scratch + +ENTRYPOINT ["/go-jsonschema"] + +COPY go-jsonschema / diff --git a/README.md b/README.md index 8ac94394..8a4cd408 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,35 @@ **go-jsonschema is a tool to generate Go data types from [JSON Schema](http://json-schema.org/) definitions.** -This tool generates Go data types and structs that corresponds to definitions in the schema, along with unmarshaling code that validates the input JSON according to the schema's validation rules. - +This tool generates Go data types and structs that corresponds to definitions in the schema, along with unmarshalling code that validates the input JSON according to the schema's validation rules. +## Badges +[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/omissis/go-jsonschema?style=for-the-badge)](https://github.com/omissis/go-jsonschema/releases/latest) +[![GitHub Workflow Status (event)](https://img.shields.io/github/workflow/status/omissis/go-jsonschema/development?style=for-the-badge)](https://github.com/omissis/go-jsonschema/actions?workflow=development) +[![License](https://img.shields.io/github/license/omissis/go-jsonschema?style=for-the-badge)](/LICENSE.md) +[![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/omissis/go-jsonschema?style=for-the-badge)](https://tip.golang.org/doc/go1.19) +[![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/omissis/go-jsonschema?style=for-the-badge)](https://github.com/omissis/go-jsonschema) +[![GitHub repo file count (file type)](https://img.shields.io/github/directory-file-count/omissis/go-jsonschema?style=for-the-badge)](https://github.com/omissis/go-jsonschema) +[![GitHub all releases](https://img.shields.io/github/downloads/omissis/go-jsonschema/total?style=for-the-badge)](https://github.com/omissis/go-jsonschema) +[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/omissis/go-jsonschema?style=for-the-badge)](https://github.com/omissis/go-jsonschema/commits) +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?style=for-the-badge)](https://conventionalcommits.org) +[![Codecov branch](https://img.shields.io/codecov/c/github/omissis/go-jsonschema/main.svg?style=for-the-badge)](https://codecov.io/gh/omissis/go-jsonschema) +[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/omissis/go-jsonschema?style=for-the-badge)](https://codeclimate.com/github/omissis/go-jsonschema) ## Installing -* **Binary install**: Get a release [here](https://github.com/atombender/go-jsonschema/releases). +* **Download**: Get a release [here](https://github.com/atombender/go-jsonschema/releases). -* **From source**: Go 1.11 or later, with Go modules enabled, is advisable in order to get the right dependencies. To install: +* **Install from source**: To install with Go 1.16+: ```shell $ go get github.com/atombender/go-jsonschema/... $ go install github.com/atombender/go-jsonschema/cmd/gojsonschema@latest ``` +* **Install with Bingo**: To install with [Bingo](https://github.com/bwplotka/bingo): + +```shell +$ bingo get github.com/atombender/go-jsonschema/cmd/gojsonschema +``` + ## Usage At its most basic: @@ -59,10 +76,10 @@ While not finished, go-jsonschema can be used today. Aside from some minor featu - [ ] Option to use `json.Number` - [x] `string` - [ ] Location identifiers (§8.2.3) - - [x] References against top-level names: `#/Definitions/someName` - - [ ] References against nested names: `#/Definitions/someName/Definitions/someOtherName` - - [x] References against top-level names in external files: `myschema.json#/Definitions/someName` - - [ ] References against nested names: `myschema.json#/Definitions/someName/Definitions/someOtherName` + - [x] References against top-level names: `#/$defs/someName` + - [ ] References against nested names: `#/$defs/someName/$defs/someOtherName` + - [x] References against top-level names in external files: `myschema.json#/$defs/someName` + - [ ] References against nested names: `myschema.json#/$defs/someName/$defs/someOtherName` - [x] Comments (§9) - Validation ([RFC draft](http://json-schema.org/latest/json-schema-validation.html)) - [ ] Schema annotations (§10) diff --git a/cmd/gojsonschema/main.go b/cmd/gojsonschema/main.go index 6b3a2ffd..7c0e0c71 100644 --- a/cmd/gojsonschema/main.go +++ b/cmd/gojsonschema/main.go @@ -1,14 +1,19 @@ package main import ( + "errors" "fmt" "os" "path/filepath" "strings" + "github.com/atombender/go-jsonschema/pkg/generator" "github.com/spf13/cobra" +) - "github.com/atombender/go-jsonschema/pkg/generator" +const ( + perm755 = 0o755 + perm644 = 0o644 ) var ( @@ -21,101 +26,103 @@ var ( capitalizations []string resolveExtensions []string yamlExtensions = []string{".yml", ".yaml"} -) -var rootCmd = &cobra.Command{ - Use: "gojsonschema FILE ...", - Short: "Generates Go code from JSON Schema files.", - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - abort("No arguments specified. Run with --help for usage.") - } + errFlagFormat = errors.New("flag must be in the format URI=PACKAGE") - if defaultPackage == "" && len(schemaPackages) == 0 { - abort("Package name not specified.") - } + rootCmd = &cobra.Command{ + Use: "gojsonschema FILE ...", + Short: "Generates Go code from JSON Schema files.", + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + abort("No arguments specified. Run with --help for usage.") + } - schemaPackageMap, err := stringSliceToStringMap(schemaPackages) - if err != nil { - abortWithErr(err) - } + if defaultPackage == "" && len(schemaPackages) == 0 { + abort("Package name not specified.") + } - schemaOutputMap, err := stringSliceToStringMap(schemaOutputs) - if err != nil { - abortWithErr(err) - } + schemaPackageMap, err := stringSliceToStringMap(schemaPackages) + if err != nil { + abortWithErr(err) + } - schemaRootTypeMap, err := stringSliceToStringMap(schemaRootTypes) - if err != nil { - abortWithErr(err) - } + schemaOutputMap, err := stringSliceToStringMap(schemaOutputs) + if err != nil { + abortWithErr(err) + } - cfg := generator.Config{ - Warner: func(message string) { - log("Warning: %s", message) - }, - Capitalizations: capitalizations, - DefaultOutputName: defaultOutput, - DefaultPackageName: defaultPackage, - SchemaMappings: []generator.SchemaMapping{}, - ResolveExtensions: resolveExtensions, - YAMLExtensions: yamlExtensions, - } - for _, id := range allKeys(schemaPackageMap, schemaOutputMap, schemaRootTypeMap) { - mapping := generator.SchemaMapping{SchemaID: id} - if s, ok := schemaPackageMap[id]; ok { - mapping.PackageName = s - } else { - mapping.PackageName = defaultPackage + schemaRootTypeMap, err := stringSliceToStringMap(schemaRootTypes) + if err != nil { + abortWithErr(err) } - if s, ok := schemaOutputMap[id]; ok { - mapping.OutputName = s + + cfg := generator.Config{ + Warner: func(message string) { + logf("Warning: %s", message) + }, + Capitalizations: capitalizations, + DefaultOutputName: defaultOutput, + DefaultPackageName: defaultPackage, + SchemaMappings: []generator.SchemaMapping{}, + ResolveExtensions: resolveExtensions, + YAMLExtensions: yamlExtensions, } - if s, ok := schemaRootTypeMap[id]; ok { - mapping.RootType = s + for _, id := range allKeys(schemaPackageMap, schemaOutputMap, schemaRootTypeMap) { + mapping := generator.SchemaMapping{SchemaID: id} + if s, ok := schemaPackageMap[id]; ok { + mapping.PackageName = s + } else { + mapping.PackageName = defaultPackage + } + if s, ok := schemaOutputMap[id]; ok { + mapping.OutputName = s + } + if s, ok := schemaRootTypeMap[id]; ok { + mapping.RootType = s + } + cfg.SchemaMappings = append(cfg.SchemaMappings, mapping) } - cfg.SchemaMappings = append(cfg.SchemaMappings, mapping) - } - - generator, err := generator.New(cfg) - if err != nil { - abortWithErr(err) - } - for _, fileName := range args { - verboseLog("Loading %s", fileName) - if err = generator.DoFile(fileName); err != nil { + generator, err := generator.New(cfg) + if err != nil { abortWithErr(err) } - } - - for fileName, source := range generator.Sources() { - if fileName != "-" { - verboseLog("Writing %s", fileName) - } - if fileName == "-" { - if _, err = os.Stdout.Write(source); err != nil { - abortWithErr(err) - } - } else { - if err := os.MkdirAll(filepath.Dir(fileName), 0755); err != nil { + for _, fileName := range args { + verboseLogf("Loading %s", fileName) + if err = generator.DoFile(fileName); err != nil { abortWithErr(err) } - w, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - abortWithErr(err) + } + + for fileName, source := range generator.Sources() { + if fileName != "-" { + verboseLogf("Writing %s", fileName) } - if _, err = w.Write(source); err != nil { - abortWithErr(err) + + if fileName == "-" { + if _, err = os.Stdout.Write(source); err != nil { + abortWithErr(err) + } + } else { + if err := os.MkdirAll(filepath.Dir(fileName), perm755); err != nil { + abortWithErr(err) + } + w, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm644) + if err != nil { + abortWithErr(err) + } + if _, err = w.Write(source); err != nil { + abortWithErr(err) + } + _ = w.Close() } - _ = w.Close() } - } - os.Exit(0) - }, -} + os.Exit(0) + }, + } +) func main() { rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, @@ -153,45 +160,53 @@ func abortWithErr(err error) { } func abort(message string) { - log("Failed: %s", message) + logf("Failed: %s", message) os.Exit(1) } func stringSliceToStringMap(s []string) (map[string]string, error) { result := make(map[string]string, len(s)) + for _, p := range s { i := strings.IndexRune(p, '=') if i == -1 { - return nil, fmt.Errorf("flag must be in the format URI=PACKAGE: %q", p) + return nil, fmt.Errorf("%w: %q", errFlagFormat, p) } + result[p[0:i]] = p[i+1:] } + return result, nil } func allKeys(in ...map[string]string) []string { type dummy struct{} + keySet := map[string]dummy{} + for _, m := range in { for k := range m { keySet[k] = dummy{} } } + result := make([]string, 0, len(keySet)) + for k := range keySet { result = append(result, k) } + return result } -func log(format string, args ...interface{}) { +func logf(format string, args ...interface{}) { fmt.Fprint(os.Stderr, "gojsonschema: ") fmt.Fprintf(os.Stderr, format, args...) fmt.Fprint(os.Stderr, "\n") } -func verboseLog(format string, args ...interface{}) { +func verboseLogf(format string, args ...interface{}) { if verbose { - log(format, args...) + logf(format, args...) } } diff --git a/go.mod b/go.mod index c80b7224..bf0245d5 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,21 @@ module github.com/atombender/go-jsonschema +go 1.19 + require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 + github.com/goccy/go-yaml v1.9.5 + github.com/mitchellh/go-wordwrap v1.0.1 github.com/pkg/errors v0.8.1 - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sanity-io/litter v1.1.0 - github.com/spf13/cobra v0.0.3 - github.com/spf13/pflag v1.0.2 // indirect - github.com/stretchr/testify v1.2.2 // indirect - gopkg.in/yaml.v2 v2.2.2 + github.com/sanity-io/litter v1.5.1 + github.com/spf13/cobra v1.1.3 ) -go 1.13 +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) diff --git a/go.sum b/go.sum index 35eea674..854b92e5 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,331 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= +github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/sanity-io/litter v1.1.0 h1:BllcKWa3VbZmOZbDCoszYLk7zCsKHz5Beossi8SUcTc= -github.com/sanity-io/litter v1.1.0/go.mod h1:CJ0VCw2q4qKU7LaQr3n7UOSHzgEMgcGco7N/SkZQPjw= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= +github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/codegen/emitter.go b/pkg/codegen/emitter.go index c8faa5e4..a9c63308 100644 --- a/pkg/codegen/emitter.go +++ b/pkg/codegen/emitter.go @@ -33,27 +33,29 @@ func (e *Emitter) Indent(n int) { if int(e.indent)+n < 0 { panic("unexpected unbalanced indentation") } + e.indent += uint(n) } func (e *Emitter) Comment(s string) { if s != "" { - limit := e.maxLineLength - uint(e.indent) + limit := e.maxLineLength - e.indent lines := strings.Split(wordwrap.WrapString(s, limit), "\n") + for _, line := range lines { - e.Println("// %s", line) + e.Printlnf("// %s", line) } } } -func (e *Emitter) Print(format string, args ...interface{}) { +func (e *Emitter) Printf(format string, args ...interface{}) { e.checkIndent() fmt.Fprintf(&e.sb, format, args...) e.start = false } -func (e *Emitter) Println(format string, args ...interface{}) { - e.Print(format, args...) +func (e *Emitter) Printlnf(format string, args ...interface{}) { + e.Printf(format, args...) e.Newline() } @@ -67,6 +69,7 @@ func (e *Emitter) checkIndent() { for i := uint(0); i < e.indent; i++ { e.sb.WriteRune('\t') } + e.start = false } } diff --git a/pkg/codegen/model.go b/pkg/codegen/model.go index 890acbf1..7e12eee8 100644 --- a/pkg/codegen/model.go +++ b/pkg/codegen/model.go @@ -1,10 +1,11 @@ package codegen import ( - "github.com/atombender/go-jsonschema/pkg/schemas" "sort" "strings" + "github.com/atombender/go-jsonschema/pkg/schemas" + "github.com/sanity-io/litter" ) @@ -55,6 +56,7 @@ func (p *Package) hasImport(q string) bool { return true } } + return false } @@ -63,17 +65,20 @@ func (p *Package) Name() string { if i := strings.LastIndex(s, "/"); i != -1 && i < len(s)-1 { return s[i+1:] } + return s } func (p *Package) Generate(out *Emitter) { out.Comment(p.Comment) - out.Println("package %s", p.Name()) + out.Printlnf("package %s", p.Name()) + if len(p.Imports) > 0 { for _, i := range p.Imports { i.Generate(out) } } + out.Newline() sorted := make([]Decl, len(p.Decls)) @@ -84,12 +89,15 @@ func (p *Package) Generate(out *Emitter) { return a.GetName() < b.GetName() } } + return false }) + for i, t := range sorted { if i > 0 { out.Newline() } + t.Generate(out) } } @@ -106,11 +114,13 @@ func (v *Var) GetName() string { } func (v *Var) Generate(out *Emitter) { - out.Print("var %s ", v.Name) + out.Printf("var %s ", v.Name) + if v.Type != nil { v.Type.Generate(out) } - out.Print(" = %s", litter.Sdump(v.Value)) + + out.Printf(" = %s", litter.Sdump(v.Value)) } // Constant is a "const = ". @@ -125,14 +135,16 @@ func (c *Constant) GetName() string { } func (c *Constant) Generate(out *Emitter) { - out.Print("const %s ", c.Name) + out.Printf("const %s ", c.Name) + if c.Type != nil { c.Type.Generate(out) } - out.Print(" = %s", litter.Sdump(c.Value)) + + out.Printf(" = %s", litter.Sdump(c.Value)) } -// Fragment is an arbitary piece of code. +// Fragment is an arbitrary piece of code. type Fragment func(*Emitter) func (f Fragment) Generate(out *Emitter) { @@ -158,9 +170,9 @@ type Import struct { func (i *Import) Generate(out *Emitter) { if i.Name != "" { - out.Println("import %s %q", i.Name, i.QualifiedName) + out.Printlnf("import %s %q", i.Name, i.QualifiedName) } else { - out.Println("import %q", i.QualifiedName) + out.Printlnf("import %q", i.QualifiedName) } } @@ -177,7 +189,7 @@ func (td *TypeDecl) GetName() string { func (td *TypeDecl) Generate(out *Emitter) { out.Comment(td.Comment) - out.Print("type %s ", td.Name) + out.Printf("type %s ", td.Name) td.Type.Generate(out) out.Newline() } @@ -194,7 +206,7 @@ type PointerType struct { func (PointerType) IsNillable() bool { return true } func (p PointerType) Generate(out *Emitter) { - out.Print("*") + out.Printf("*") p.Type.Generate(out) } @@ -205,7 +217,7 @@ type ArrayType struct { func (ArrayType) IsNillable() bool { return true } func (a ArrayType) Generate(out *Emitter) { - out.Print("[]") + out.Printf("[]") a.Type.Generate(out) } @@ -224,10 +236,11 @@ func (t NamedType) IsNillable() bool { func (t NamedType) Generate(out *Emitter) { if t.Package != nil { - out.Print(t.Package.Name()) - out.Print(".") + out.Printf(t.Package.Name()) + out.Printf(".") } - out.Print(t.Decl.Name) + + out.Printf(t.Decl.Name) } type PrimitiveType struct { @@ -237,7 +250,7 @@ type PrimitiveType struct { func (PrimitiveType) IsNillable() bool { return false } func (p PrimitiveType) Generate(out *Emitter) { - out.Print(p.Type) + out.Printf(p.Type) } type CustomNameType struct { @@ -247,7 +260,7 @@ type CustomNameType struct { func (CustomNameType) IsNillable() bool { return false } func (p CustomNameType) Generate(out *Emitter) { - out.Print(p.Type) + out.Printf(p.Type) } type MapType struct { @@ -257,9 +270,9 @@ type MapType struct { func (MapType) IsNillable() bool { return true } func (p MapType) Generate(out *Emitter) { - out.Print("map[") + out.Printf("map[") p.KeyType.Generate(out) - out.Print("]") + out.Printf("]") p.ValueType.Generate(out) } @@ -268,7 +281,7 @@ type EmptyInterfaceType struct{} func (EmptyInterfaceType) IsNillable() bool { return true } func (EmptyInterfaceType) Generate(out *Emitter) { - out.Print("interface{}") + out.Printf("interface{}") } type NullType struct{} @@ -276,7 +289,7 @@ type NullType struct{} func (NullType) IsNillable() bool { return true } func (NullType) Generate(out *Emitter) { - out.Print("interface{}") + out.Printf("interface{}") } type StructType struct { @@ -291,19 +304,23 @@ func (s *StructType) AddField(f StructField) { } func (s *StructType) Generate(out *Emitter) { - out.Println("struct {") + out.Printlnf("struct {") out.Indent(1) + i := 0 + for _, f := range s.Fields { if i > 0 { out.Newline() } + f.Generate(out) out.Newline() i++ } + out.Indent(-1) - out.Print("}") + out.Printf("}") } type StructField struct { @@ -322,9 +339,10 @@ func (f *StructField) GetName() string { func (f *StructField) Generate(out *Emitter) { out.Comment(f.Comment) - out.Print("%s ", f.Name) + out.Printf("%s ", f.Name) f.Type.Generate(out) + if f.Tags != "" { - out.Print(" `%s`", f.Tags) + out.Printf(" `%s`", f.Tags) } } diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go index 62451096..f368b83d 100644 --- a/pkg/codegen/utils.go +++ b/pkg/codegen/utils.go @@ -6,10 +6,16 @@ import ( "github.com/atombender/go-jsonschema/pkg/schemas" ) +var ( + errUnexpectedType = fmt.Errorf("unexpected type") + errUnknownJSONSchemaType = fmt.Errorf("unknown JSON Schema type") +) + func WrapTypeInPointer(t Type) Type { if isPointerType(t) { return t } + return &PointerType{Type: t} } @@ -17,8 +23,10 @@ func isPointerType(t Type) bool { switch x := t.(type) { case *PointerType: return true + case *NamedType: return isPointerType(x.Decl.Type) + default: return false } @@ -28,34 +36,42 @@ func PrimitiveTypeFromJSONSchemaType(jsType string, pointer bool) (Type, error) switch jsType { case schemas.TypeNameString: t := PrimitiveType{"string"} - if pointer == true { + if pointer { return WrapTypeInPointer(t), nil } + return t, nil + case schemas.TypeNameNumber: t := PrimitiveType{"float64"} - if pointer == true { + if pointer { return WrapTypeInPointer(t), nil } + return t, nil + case schemas.TypeNameInteger: t := PrimitiveType{"int"} - if pointer == true { + if pointer { return WrapTypeInPointer(t), nil } return t, nil + case schemas.TypeNameBoolean: t := PrimitiveType{"bool"} - if pointer == true { + if pointer { return WrapTypeInPointer(t), nil } return t, nil + case schemas.TypeNameNull: return NullType{}, nil + case schemas.TypeNameObject, schemas.TypeNameArray: - return nil, fmt.Errorf("unexpected type %q here", jsType) + return nil, fmt.Errorf("%w %q here", errUnexpectedType, jsType) } - return nil, fmt.Errorf("unknown JSON Schema type %q", jsType) + + return nil, fmt.Errorf("%w %q", errUnknownJSONSchemaType, jsType) } diff --git a/pkg/generator/generate.go b/pkg/generator/generate.go index 0dd36daa..61cf4925 100644 --- a/pkg/generator/generate.go +++ b/pkg/generator/generate.go @@ -1,6 +1,7 @@ package generator import ( + "errors" "fmt" "go/format" "os" @@ -10,7 +11,6 @@ import ( "github.com/atombender/go-jsonschema/pkg/codegen" "github.com/atombender/go-jsonschema/pkg/schemas" - "github.com/pkg/errors" ) type Config struct { @@ -38,6 +38,25 @@ type Generator struct { warner func(string) } +const ( + varNamePlainStruct = "plain" + varNameRawMap = "raw" + interfaceTypeName = "interface{}" +) + +var ( + errSchemaHasNoRoot = errors.New("schema has no root") + errArrayPropertyItems = errors.New("array property must have 'items' set to a type") + errEnumArrCannotBeEmpty = errors.New("enum array cannot be empty") + errEnumNonPrimitiveVal = errors.New("enum has non-primitive value") + errCouldNotResolveSchema = errors.New("could not resolve schema") + errMapURIToPackageName = errors.New("unable to map schema URI to Go package name") + errExpectedNamedType = errors.New("expected named type") + errUnsupportedRefFormat = errors.New("unsupported $ref format") + errConflictSameFile = errors.New("conflict: same file") + errDefinitionDoesNotExistInSchema = errors.New("definition does not exist in schema") +) + func New(config Config) (*Generator, error) { return &Generator{ config: config, @@ -49,12 +68,16 @@ func New(config Config) (*Generator, error) { } func (g *Generator) Sources() map[string][]byte { + var maxLineLength uint = 80 + sources := make(map[string]*strings.Builder, len(g.outputs)) + for _, output := range g.outputs { if output.file.FileName == "" { continue } - emitter := codegen.NewEmitter(80) + + emitter := codegen.NewEmitter(maxLineLength) output.file.Generate(emitter) sb, ok := sources[output.file.FileName] @@ -62,54 +85,76 @@ func (g *Generator) Sources() map[string][]byte { sb = &strings.Builder{} sources[output.file.FileName] = sb } + _, _ = sb.WriteString(emitter.String()) } result := make(map[string][]byte, len(sources)) + for f, sb := range sources { source := []byte(sb.String()) + src, err := format.Source(source) if err != nil { g.config.Warner(fmt.Sprintf("The generated code could not be formatted automatically; "+ "falling back to unformatted: %s", err)) + src = source } + result[f] = src } + return result } func (g *Generator) DoFile(fileName string) error { var err error + var schema *schemas.Schema + if fileName == "-" { schema, err = schemas.FromJSONReader(os.Stdin) if err != nil { - return errors.Wrap(err, "error parsing from standard input") + return fmt.Errorf("error parsing from standard input: %w", err) } } else { schema, err = g.parseFile(fileName) if err != nil { - return errors.Wrapf(err, "error parsing from file %s", fileName) + return fmt.Errorf("error parsing from file %s: %w", fileName, err) } } + return g.addFile(fileName, schema) } func (g *Generator) parseFile(fileName string) (*schemas.Schema, error) { - // TODO: Refactor into some kind of loader + // TODO: Refactor into some kind of loader. isYAML := false + for _, yamlExt := range g.config.YAMLExtensions { if strings.HasSuffix(fileName, yamlExt) { isYAML = true + break } } + if isYAML { - return schemas.FromYAMLFile(fileName) - } else { - return schemas.FromJSONFile(fileName) + sc, err := schemas.FromYAMLFile(fileName) + if err != nil { + return nil, fmt.Errorf("error parsing YAML file %s: %w", fileName, err) + } + + return sc, nil + } + + sc, err := schemas.FromJSONFile(fileName) + if err != nil { + return nil, fmt.Errorf("error parsing JSON file %s: %w", fileName, err) } + + return sc, nil } func (g *Generator) addFile(fileName string, schema *schemas.Schema) error { @@ -135,15 +180,16 @@ func (g *Generator) loadSchemaFromFile(fileName, parentFileName string) (*schema for i, ext := range exts { qualified := fileName + ext - // Poor man's resolving loop + // Poor man's resolving loop. if i < len(exts)-1 && !fileExists(qualified) { continue } var err error + qualified, err = filepath.EvalSymlinks(qualified) if err != nil { - return nil, err + return nil, fmt.Errorf("error resolving symlinks in %s: %w", qualified, err) } if schema, ok := g.schemaCacheByFileName[qualified]; ok { @@ -154,14 +200,17 @@ func (g *Generator) loadSchemaFromFile(fileName, parentFileName string) (*schema if err != nil { return nil, err } + g.schemaCacheByFileName[qualified] = schema if err = g.addFile(qualified, schema); err != nil { return nil, err } + return schema, nil } - return nil, fmt.Errorf("could not resolve schema %q", fileName) + + return nil, fmt.Errorf("%w %q", errCouldNotResolveSchema, fileName) } func (g *Generator) getRootTypeName(schema *schemas.Schema, fileName string) string { @@ -170,6 +219,7 @@ func (g *Generator) getRootTypeName(schema *schemas.Schema, fileName string) str return m.RootType } } + return g.identifierFromFileName(fileName) } @@ -183,22 +233,25 @@ func (g *Generator) findOutputFileForSchemaID(id string) (*output, error) { return g.beginOutput(id, m.OutputName, m.PackageName) } } + return g.beginOutput(id, g.config.DefaultOutputName, g.config.DefaultPackageName) } func (g *Generator) beginOutput( id string, - outputName, packageName string) (*output, error) { + outputName, packageName string, +) (*output, error) { if packageName == "" { - return nil, fmt.Errorf("unable to map schema URI %q to a Go package name", id) + return nil, fmt.Errorf("%w: %q", errMapURIToPackageName, id) } for _, o := range g.outputs { if o.file.FileName == outputName && o.file.Package.QualifiedName != packageName { return nil, fmt.Errorf( - "conflict: same file (%s) mapped to two different Go packages (%q and %q) for schema %q", - o.file.FileName, o.file.Package.QualifiedName, packageName, id) + "%w (%s) mapped to two different Go packages (%q and %q) for schema %q", + errConflictSameFile, o.file.FileName, o.file.Package.QualifiedName, packageName, id) } + if o.file.FileName == outputName && o.file.Package.QualifiedName == packageName { return o, nil } @@ -218,6 +271,7 @@ func (g *Generator) beginOutput( declsByName: map[string]*codegen.TypeDecl{}, } g.outputs[id] = output + return output, nil } @@ -225,6 +279,7 @@ func (g *Generator) makeEnumConstantName(typeName, value string) string { if strings.ContainsAny(typeName[len(typeName)-1:], "0123456789") { return typeName + "_" + g.identifierize(value) } + return typeName + g.identifierize(value) } @@ -232,11 +287,13 @@ func (g *Generator) identifierFromFileName(fileName string) string { s := filepath.Base(fileName) for _, ext := range g.config.ResolveExtensions { trimmed := strings.TrimSuffix(s, ext) - if trimmed == s { + if trimmed != s { + s = trimmed + break } - s = trimmed } + return g.identifierize(s) } @@ -245,15 +302,18 @@ func (g *Generator) identifierize(s string) string { return "Blank" } - // FIXME: Better handling of non-identifier chars + // FIXME: Better handling of non-identifier chars. var sb strings.Builder for _, part := range splitIdentifierByCaseAndSeparators(s) { _, _ = sb.WriteString(g.capitalize(part)) } + ident := sb.String() + if !unicode.IsLetter(rune(ident[0])) { ident = "A" + ident } + return ident } @@ -261,11 +321,13 @@ func (g *Generator) capitalize(s string) string { if len(s) == 0 { return "" } + for _, c := range g.config.Capitalizations { - if strings.ToLower(c) == strings.ToLower(s) { + if strings.EqualFold(c, s) { return c } } + return strings.ToUpper(s[0:1]) + s[1:] } @@ -278,16 +340,18 @@ type schemaGenerator struct { func (g *schemaGenerator) generateRootType() error { if g.schema.ObjectAsType == nil { - return errors.New("schema has no root") + return errSchemaHasNoRoot } for _, name := range sortDefinitionsByName(g.schema.Definitions) { def := g.schema.Definitions[name] + _, err := g.generateDeclaredType(def, newNameScope(g.identifierize(name))) if err != nil { return err } } + if len(g.schema.ObjectAsType.Type) == 0 { return nil } @@ -298,6 +362,7 @@ func (g *schemaGenerator) generateRootType() error { } _, err := g.generateDeclaredType((*schemas.Type)(g.schema.ObjectAsType), newNameScope(rootTypeName)) + return err } @@ -307,18 +372,33 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro fileName = ref } else { fileName, scope = ref[0:i], ref[i+1:] - if !strings.HasPrefix(strings.ToLower(scope), "/definitions/") { - return nil, fmt.Errorf("unsupported $ref format; must point to definition within file: %q", ref) + var prefix string + lowercaseScope := strings.ToLower(scope) + for _, currentPrefix := range []string{ + "/$defs/", // Draft-handrews-json-schema-validation-02. + "/definitions/", // Legacy. + } { + if strings.HasPrefix(lowercaseScope, currentPrefix) { + prefix = currentPrefix + + break + } + } + + if len(prefix) == 0 { + return nil, fmt.Errorf("%w; must point to definition within file: %q", errUnsupportedRefFormat, ref) } - defName = scope[13:] + defName = scope[len(prefix):] } var schema *schemas.Schema + if fileName != "" { var err error + schema, err = g.loadSchemaFromFile(fileName, g.schemaFileName) if err != nil { - return nil, fmt.Errorf("could not follow $ref %q to file %q: %s", ref, fileName, err) + return nil, fmt.Errorf("could not follow $ref %q to file %q: %w", ref, fileName, err) } } else { schema = g.schema @@ -330,22 +410,26 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro } var def *schemas.Type + if defName != "" { - // TODO: Support nested definitions + // TODO: Support nested definitions. var ok bool + def, ok = schema.Definitions[defName] if !ok { - return nil, fmt.Errorf("definition %q (from ref %q) does not exist in schema", defName, ref) + return nil, fmt.Errorf("%w: %q (from ref %q)", errDefinitionDoesNotExistInSchema, defName, ref) } + if len(def.Type) == 0 && len(def.Properties) == 0 { return &codegen.EmptyInterfaceType{}, nil } + defName = g.identifierize(defName) } else { def = (*schemas.Type)(schema.ObjectAsType) defName = g.getRootTypeName(schema, fileName) if len(def.Type) == 0 { - // Minor hack to make definitions default to being objects + // Minor hack to make definitions default to being objects. def.Type = schemas.TypeList{schemas.TypeNameObject} } } @@ -359,6 +443,7 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro } var sg *schemaGenerator + if fileName != "" { output, err := g.findOutputFileForSchemaID(schema.ID) if err != nil { @@ -380,10 +465,14 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro return nil, err } - nt := t.(*codegen.NamedType) + nt, ok := t.(*codegen.NamedType) + if !ok { + return nil, fmt.Errorf("%w: got %T", errExpectedNamedType, t) + } if isCycle { g.warner(fmt.Sprintf("Cycle detected; must wrap type %s in pointer", nt.Decl.Name)) + t = codegen.WrapTypeInPointer(t) } @@ -392,12 +481,16 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro } var imp *codegen.Import + for _, i := range g.output.file.Package.Imports { + i := i if i.Name == sg.output.file.Package.Name() && i.QualifiedName == sg.output.file.Package.QualifiedName { imp = &i + break } } + if imp == nil { g.output.file.Package.AddImport(sg.output.file.Package.QualifiedName, sg.output.file.Package.Name()) } @@ -409,7 +502,8 @@ func (g *schemaGenerator) generateReferencedType(ref string) (codegen.Type, erro } func (g *schemaGenerator) generateDeclaredType( - t *schemas.Type, scope nameScope) (codegen.Type, error) { + t *schemas.Type, scope nameScope, +) (codegen.Type, error) { if decl, ok := g.output.declsBySchema[t]; ok { return &codegen.NamedType{Decl: decl}, nil } @@ -429,12 +523,15 @@ func (g *schemaGenerator) generateDeclaredType( if err != nil { return nil, err } + if isNamedType(theType) { - // Don't declare named types under a new name + // Don't declare named types under a new name. delete(g.output.declsBySchema, t) delete(g.output.declsByName, decl.Name) + return theType, nil } + decl.Type = theType g.output.file.Package.AddDecl(&decl) @@ -444,6 +541,7 @@ func (g *schemaGenerator) generateDeclaredType( for _, f := range structType.RequiredJSONFields { validators = append(validators, &requiredValidator{f, decl.Name}) } + for _, f := range structType.Fields { if f.DefaultValue != nil { validators = append(validators, &defaultValidator{ @@ -453,6 +551,7 @@ func (g *schemaGenerator) generateDeclaredType( defaultValue: f.DefaultValue, }) } + if _, ok := f.Type.(codegen.NullType); ok { validators = append(validators, &nullTypeValidator{ fieldName: f.Name, @@ -468,17 +567,16 @@ func (g *schemaGenerator) generateDeclaredType( jsonName: f.JSONName, arrayDepth: arrayDepth, }) + break - } else { - if f.SchemaType.MinItems != 0 || f.SchemaType.MaxItems != 0 { - validators = append(validators, &arrayValidator{ - fieldName: f.Name, - jsonName: f.JSONName, - arrayDepth: arrayDepth, - minItems: f.SchemaType.MinItems, - maxItems: f.SchemaType.MaxItems, - }) - } + } else if f.SchemaType.MinItems != 0 || f.SchemaType.MaxItems != 0 { + validators = append(validators, &arrayValidator{ + fieldName: f.Name, + jsonName: f.JSONName, + arrayDepth: arrayDepth, + minItems: f.SchemaType.MinItems, + maxItems: f.SchemaType.MaxItems, + }) } t = v.Type @@ -490,6 +588,7 @@ func (g *schemaGenerator) generateDeclaredType( for _, v := range validators { if v.desc().hasError { g.output.file.Package.AddImport("fmt", "") + break } } @@ -498,10 +597,10 @@ func (g *schemaGenerator) generateDeclaredType( g.output.file.Package.AddDecl(&codegen.Method{ Impl: func(out *codegen.Emitter) { out.Comment("UnmarshalJSON implements json.Unmarshaler.") - out.Println("func (j *%s) UnmarshalJSON(b []byte) error {", decl.Name) + out.Printlnf("func (j *%s) UnmarshalJSON(b []byte) error {", decl.Name) out.Indent(1) - out.Println("var %s map[string]interface{}", varNameRawMap) - out.Println("if err := json.Unmarshal(b, &%s); err != nil { return err }", + out.Printlnf("var %s map[string]interface{}", varNameRawMap) + out.Printlnf("if err := json.Unmarshal(b, &%s); err != nil { return err }", varNameRawMap) for _, v := range validators { if v.desc().beforeJSONUnmarshal { @@ -509,9 +608,9 @@ func (g *schemaGenerator) generateDeclaredType( } } - out.Println("type Plain %s", decl.Name) - out.Println("var %s Plain", varNamePlainStruct) - out.Println("if err := json.Unmarshal(b, &%s); err != nil { return err }", + out.Printlnf("type Plain %s", decl.Name) + out.Printlnf("var %s Plain", varNamePlainStruct) + out.Printlnf("if err := json.Unmarshal(b, &%s); err != nil { return err }", varNamePlainStruct) for _, v := range validators { @@ -520,10 +619,10 @@ func (g *schemaGenerator) generateDeclaredType( } } - out.Println("*j = %s(%s)", decl.Name, varNamePlainStruct) - out.Println("return nil") + out.Printlnf("*j = %s(%s)", decl.Name, varNamePlainStruct) + out.Printlnf("return nil") out.Indent(-1) - out.Println("}") + out.Printlnf("}") }, }) } @@ -533,75 +632,102 @@ func (g *schemaGenerator) generateDeclaredType( } func (g *schemaGenerator) generateType( - t *schemas.Type, scope nameScope) (codegen.Type, error) { - var typeIndex = 0 + t *schemas.Type, scope nameScope, +) (codegen.Type, error) { + typeIndex := 0 + var typeShouldBePointer bool + two := 2 + if ext := t.GoJSONSchemaExtension; ext != nil { for _, pkg := range ext.Imports { g.output.file.Package.AddImport(pkg, "") } + if ext.Type != nil { return &codegen.CustomNameType{Type: *ext.Type}, nil } } + if t.Enum != nil { return g.generateEnumType(t, scope) } + if t.Ref != "" { return g.generateReferencedType(t.Ref) } + if len(t.Type) == 0 { return codegen.EmptyInterfaceType{}, nil } - if len(t.Type) == 2 { + + if len(t.Type) == two { for i, t := range t.Type { if t == "null" { typeShouldBePointer = true + continue } + typeIndex = i } } else if len(t.Type) != 1 { - // TODO: Support validation for properties with multiple types + // TODO: Support validation for properties with multiple types. g.warner("Property has multiple types; will be represented as interface{} with no validation") + return codegen.EmptyInterfaceType{}, nil } switch t.Type[typeIndex] { case schemas.TypeNameArray: if t.Items == nil { - return nil, errors.New("array property must have 'items' set to a type") + return nil, errArrayPropertyItems } + elemType, err := g.generateType(t.Items, scope.add("Elem")) if err != nil { return nil, err } + return codegen.ArrayType{Type: elemType}, nil + case schemas.TypeNameObject: return g.generateStructType(t, scope) + case schemas.TypeNameNull: return codegen.EmptyInterfaceType{}, nil + default: - return codegen.PrimitiveTypeFromJSONSchemaType(t.Type[typeIndex], typeShouldBePointer) + cg, err := codegen.PrimitiveTypeFromJSONSchemaType(t.Type[typeIndex], typeShouldBePointer) + if err != nil { + return nil, fmt.Errorf("invalid type %q: %w", t.Type[typeIndex], err) + } + + return cg, nil } } func (g *schemaGenerator) generateStructType( t *schemas.Type, - scope nameScope) (codegen.Type, error) { + scope nameScope, +) (codegen.Type, error) { if len(t.Properties) == 0 { if len(t.Required) > 0 { g.warner("Object type with no properties has required fields; " + "skipping validation code for them since we don't know their types") } + valueType := codegen.Type(codegen.EmptyInterfaceType{}) + var err error + if t.AdditionalProperties != nil { if valueType, err = g.generateType(t.AdditionalProperties, nil); err != nil { return nil, err } } + return &codegen.MapType{ KeyType: codegen.PrimitiveType{Type: "string"}, ValueType: valueType, @@ -616,15 +742,18 @@ func (g *schemaGenerator) generateStructType( uniqueNames := make(map[string]int, len(t.Properties)) var structType codegen.StructType + for _, name := range sortPropertiesByName(t.Properties) { prop := t.Properties[name] isRequired := requiredNames[name] fieldName := g.identifierize(name) + if ext := prop.GoJSONSchemaExtension; ext != nil { for _, pkg := range ext.Imports { g.output.file.Package.AddImport(pkg, "") } + if ext.Identifier != nil { fieldName = *ext.Identifier } @@ -658,86 +787,122 @@ func (g *schemaGenerator) generateStructType( } var err error + structField.Type, err = g.generateTypeInline(prop, scope.add(structField.Name)) if err != nil { - return nil, fmt.Errorf("could not generate type for field %q: %s", name, err) + return nil, fmt.Errorf("could not generate type for field %q: %w", name, err) } - if prop.Default != nil { + switch { + case prop.Default != nil: structField.DefaultValue = prop.Default - } else if isRequired { - structType.RequiredJSONFields = append(structType.RequiredJSONFields, structField.JSONName) - } else { - // Optional, so must be pointer - if !structField.Type.IsNillable() { + + default: + if isRequired { + structType.RequiredJSONFields = append(structType.RequiredJSONFields, structField.JSONName) + } else if !structField.Type.IsNillable() { structField.Type = codegen.WrapTypeInPointer(structField.Type) } } structType.AddField(structField) } + return &structType, nil } func (g *schemaGenerator) generateTypeInline( t *schemas.Type, - scope nameScope) (codegen.Type, error) { + scope nameScope, +) (codegen.Type, error) { + two := 2 + if t.Enum == nil && t.Ref == "" { if ext := t.GoJSONSchemaExtension; ext != nil { for _, pkg := range ext.Imports { g.output.file.Package.AddImport(pkg, "") } + if ext.Type != nil { return &codegen.CustomNameType{Type: *ext.Type}, nil } } - if len(t.Type) > 1 { + typeIndex := 0 + + var typeShouldBePointer bool + + if len(t.Type) == two { + for i, t := range t.Type { + if t == "null" { + typeShouldBePointer = true + + continue + } + + typeIndex = i + } + } else if len(t.Type) > 1 { g.warner("Property has multiple types; will be represented as interface{} with no validation") + return codegen.EmptyInterfaceType{}, nil } + if len(t.Type) == 0 { return codegen.EmptyInterfaceType{}, nil } - if schemas.IsPrimitiveType(t.Type[0]) { - return codegen.PrimitiveTypeFromJSONSchemaType(t.Type[0], false) + if schemas.IsPrimitiveType(t.Type[typeIndex]) { + cg, err := codegen.PrimitiveTypeFromJSONSchemaType(t.Type[typeIndex], typeShouldBePointer) + if err != nil { + return nil, fmt.Errorf("invalid type %q: %w", t.Type[typeIndex], err) + } + + return cg, nil } - if t.Type[0] == schemas.TypeNameArray { + if t.Type[typeIndex] == schemas.TypeNameArray { var theType codegen.Type + if t.Items == nil { theType = codegen.EmptyInterfaceType{} } else { var err error + theType, err = g.generateTypeInline(t.Items, scope.add("Elem")) if err != nil { return nil, err } } + return &codegen.ArrayType{Type: theType}, nil } } + return g.generateDeclaredType(t, scope) } func (g *schemaGenerator) generateEnumType( - t *schemas.Type, scope nameScope) (codegen.Type, error) { + t *schemas.Type, scope nameScope, +) (codegen.Type, error) { if len(t.Enum) == 0 { - return nil, errors.New("enum array cannot be empty") + return nil, errEnumArrCannotBeEmpty } var wrapInStruct bool + var enumType codegen.Type + if len(t.Type) == 1 { var err error if enumType, err = codegen.PrimitiveTypeFromJSONSchemaType(t.Type[0], false); err != nil { - return nil, err + return nil, fmt.Errorf("invalid type %q: %w", t.Type[0], err) } - wrapInStruct = t.Type[0] == schemas.TypeNameNull // Null uses interface{}, which cannot have methods + + wrapInStruct = t.Type[0] == schemas.TypeNameNull // Null uses interface{}, which cannot have methods. } else { if len(t.Type) > 1 { - // TODO: Support multiple types + // TODO: Support multiple types. g.warner("Enum defined with multiple types; ignoring it and using enum values instead") } @@ -745,7 +910,7 @@ func (g *schemaGenerator) generateEnumType( for _, v := range t.Enum { var valueType string if v == nil { - valueType = "interface{}" + valueType = interfaceTypeName } else { switch v.(type) { case string: @@ -755,23 +920,26 @@ func (g *schemaGenerator) generateEnumType( case bool: valueType = "bool" default: - return nil, fmt.Errorf("enum has non-primitive value %v", v) + return nil, fmt.Errorf("%w %v", errEnumNonPrimitiveVal, v) } } if primitiveType == "" { primitiveType = valueType } else if primitiveType != valueType { - primitiveType = "interface{}" + primitiveType = interfaceTypeName + break } } - if primitiveType == "interface{}" { + if primitiveType == interfaceTypeName { wrapInStruct = true } enumType = codegen.PrimitiveType{Type: primitiveType} } + if wrapInStruct { g.warner("Enum field wrapped in struct in order to store values of multiple types") + enumType = &codegen.StructType{ Fields: []codegen.StructField{ { @@ -802,11 +970,11 @@ func (g *schemaGenerator) generateEnumType( g.output.file.Package.AddDecl(&codegen.Method{ Impl: func(out *codegen.Emitter) { out.Comment("MarshalJSON implements json.Marshaler.") - out.Println("func (j *%s) MarshalJSON() ([]byte, error) {", enumDecl.Name) + out.Printlnf("func (j *%s) MarshalJSON() ([]byte, error) {", enumDecl.Name) out.Indent(1) - out.Println("return json.Marshal(j.Value)") + out.Printlnf("return json.Marshal(j.Value)") out.Indent(-1) - out.Println("}") + out.Printlnf("}") }, }) } @@ -817,36 +985,36 @@ func (g *schemaGenerator) generateEnumType( g.output.file.Package.AddDecl(&codegen.Method{ Impl: func(out *codegen.Emitter) { out.Comment("UnmarshalJSON implements json.Unmarshaler.") - out.Println("func (j *%s) UnmarshalJSON(b []byte) error {", enumDecl.Name) + out.Printlnf("func (j *%s) UnmarshalJSON(b []byte) error {", enumDecl.Name) out.Indent(1) - out.Print("var v ") + out.Printf("var v ") enumType.Generate(out) out.Newline() varName := "v" if wrapInStruct { varName += ".Value" } - out.Println("if err := json.Unmarshal(b, &%s); err != nil { return err }", varName) - out.Println("var ok bool") - out.Println("for _, expected := range %s {", valueConstant.Name) - out.Println("if reflect.DeepEqual(%s, expected) { ok = true; break }", varName) - out.Println("}") - out.Println("if !ok {") - out.Println(`return fmt.Errorf("invalid value (expected one of %%#v): %%#v", %s, %s)`, + out.Printlnf("if err := json.Unmarshal(b, &%s); err != nil { return err }", varName) + out.Printlnf("var ok bool") + out.Printlnf("for _, expected := range %s {", valueConstant.Name) + out.Printlnf("if reflect.DeepEqual(%s, expected) { ok = true; break }", varName) + out.Printlnf("}") + out.Printlnf("if !ok {") + out.Printlnf(`return fmt.Errorf("invalid value (expected one of %%#v): %%#v", %s, %s)`, valueConstant.Name, varName) - out.Println("}") - out.Println(`*j = %s(v)`, enumDecl.Name) - out.Println(`return nil`) + out.Printlnf("}") + out.Printlnf(`*j = %s(v)`, enumDecl.Name) + out.Printlnf(`return nil`) out.Indent(-1) - out.Println("}") + out.Printlnf("}") }, }) - // TODO: May be aliased string type + // TODO: May be aliased string type. if prim, ok := enumType.(codegen.PrimitiveType); ok && prim.Type == "string" { for _, v := range t.Enum { if s, ok := v.(string); ok { - // TODO: Make sure the name is unique across scope + // TODO: Make sure the name is unique across scope. g.output.file.Package.AddDecl(&codegen.Constant{ Name: g.makeEnumConstantName(enumDecl.Name, s), Type: &codegen.NamedType{Decl: &enumDecl}, @@ -867,26 +1035,26 @@ type output struct { } func (o *output) uniqueTypeName(name string) string { - if _, ok := o.declsByName[name]; !ok { + v, ok := o.declsByName[name] + + if !ok || (ok && v.Type == nil) { return name } + count := 1 + for { suffixed := fmt.Sprintf("%s_%d", name, count) if _, ok := o.declsByName[suffixed]; !ok { o.warner(fmt.Sprintf( "Multiple types map to the name %q; declaring duplicate as %q instead", name, suffixed)) + return suffixed } count++ } } -type cachedEnum struct { - values []interface{} - enum *codegen.TypeDecl -} - type qualifiedDefinition struct { schema *schemas.Schema name string @@ -906,15 +1074,12 @@ func (ns nameScope) add(s string) nameScope { result := make(nameScope, len(ns)+1) copy(result, ns) result[len(result)-1] = s + return result } -var ( - varNamePlainStruct = "plain" - varNameRawMap = "raw" -) - func fileExists(fileName string) bool { _, err := os.Stat(fileName) + return err == nil || !os.IsNotExist(err) } diff --git a/pkg/generator/utils.go b/pkg/generator/utils.go index a296badf..0182ed2d 100644 --- a/pkg/generator/utils.go +++ b/pkg/generator/utils.go @@ -1,8 +1,6 @@ package generator import ( - "crypto/sha256" - "fmt" "sort" "unicode" @@ -10,26 +8,13 @@ import ( "github.com/atombender/go-jsonschema/pkg/schemas" ) -func hashArrayOfValues(values []interface{}) string { - sorted := make([]interface{}, len(values)) - copy(sorted, values) - sort.Slice(sorted, func(i, j int) bool { - return fmt.Sprintf("%#v", sorted[i]) < fmt.Sprintf("%#v", sorted[j]) - }) - - h := sha256.New() - for _, v := range sorted { - h.Write([]byte(fmt.Sprintf("%#v", v))) - } - return fmt.Sprintf("%x", h.Sum(nil)) -} - func splitIdentifierByCaseAndSeparators(s string) []string { if len(s) == 0 { return nil } type state int + const ( stateNothing state = iota stateLower @@ -39,20 +24,28 @@ func splitIdentifierByCaseAndSeparators(s string) []string { ) var result []string + currState, j := stateNothing, 0 + for i := 0; i < len(s); i++ { var nextState state + c := rune(s[i]) + switch { case unicode.IsLower(c): nextState = stateLower + case unicode.IsUpper(c): nextState = stateUpper + case unicode.IsNumber(c): nextState = stateNumber + default: nextState = stateDelimiter } + if nextState != currState { if currState == stateDelimiter { j = i @@ -62,12 +55,15 @@ func splitIdentifierByCaseAndSeparators(s string) []string { } j = i } + currState = nextState } } + if currState != stateDelimiter && len(s)-j > 0 { result = append(result, s[j:]) } + return result } @@ -76,16 +72,21 @@ func sortPropertiesByName(props map[string]*schemas.Type) []string { for name := range props { names = append(names, name) } + sort.Strings(names) + return names } func sortDefinitionsByName(defs schemas.Definitions) []string { names := make([]string, 0, len(defs)) + for name := range defs { names = append(names, name) } + sort.Strings(names) + return names } @@ -93,10 +94,12 @@ func isNamedType(t codegen.Type) bool { switch x := t.(type) { case *codegen.NamedType: return true + case *codegen.PointerType: if _, ok := x.Type.(*codegen.NamedType); ok { return true } } + return false } diff --git a/pkg/generator/validator.go b/pkg/generator/validator.go index 32216d98..24d8e302 100644 --- a/pkg/generator/validator.go +++ b/pkg/generator/validator.go @@ -33,11 +33,11 @@ type requiredValidator struct { } func (v *requiredValidator) generate(out *codegen.Emitter) { - out.Println(`if v, ok := %s["%s"]; !ok || v == nil {`, varNameRawMap, v.jsonName) + out.Printlnf(`if v, ok := %s["%s"]; !ok || v == nil {`, varNameRawMap, v.jsonName) out.Indent(1) - out.Println(`return fmt.Errorf("field %s in %s: required")`, v.jsonName, v.declName) + out.Printlnf(`return fmt.Errorf("field %s in %s: required")`, v.jsonName, v.declName) out.Indent(-1) - out.Println("}") + out.Printlnf("}") } func (v *requiredValidator) desc() *validatorDesc { @@ -56,13 +56,16 @@ type nullTypeValidator struct { func (v *nullTypeValidator) generate(out *codegen.Emitter) { value := fmt.Sprintf("%s.%s", varNamePlainStruct, v.fieldName) fieldName := v.jsonName + var indexes []string + for i := 0; i < v.arrayDepth; i++ { index := fmt.Sprintf("i%d", i) indexes = append(indexes, index) - out.Println(`for %s := range %s {`, index, value) + out.Printlnf(`for %s := range %s {`, index, value) value += fmt.Sprintf("[%s]", index) fieldName += "[%d]" + out.Indent(1) } @@ -71,15 +74,15 @@ func (v *nullTypeValidator) generate(out *codegen.Emitter) { fieldName = fmt.Sprintf(`fmt.Sprintf(%s, %s)`, fieldName, strings.Join(indexes, ", ")) } - out.Println(`if %s != nil {`, value) + out.Printlnf(`if %s != nil {`, value) out.Indent(1) - out.Println(`return fmt.Errorf("field %%s: must be null", %s)`, fieldName) + out.Printlnf(`return fmt.Errorf("field %%s: must be null", %s)`, fieldName) out.Indent(-1) - out.Println("}") + out.Printlnf("}") for i := 0; i < v.arrayDepth; i++ { out.Indent(-1) - out.Println("}") + out.Printlnf("}") } } @@ -100,33 +103,40 @@ type defaultValidator struct { func (v *defaultValidator) generate(out *codegen.Emitter) { defaultValue, err := v.tryDumpDefaultSlice(out.MaxLineLength()) if err != nil { - // fallback to sdump in case we couldn't dump it properly + // Fallback to sdump in case we couldn't dump it properly. defaultValue = litter.Sdump(v.defaultValue) } - out.Println(`if v, ok := %s["%s"]; !ok || v == nil {`, varNameRawMap, v.jsonName) + out.Printlnf(`if v, ok := %s["%s"]; !ok || v == nil {`, varNameRawMap, v.jsonName) out.Indent(1) - out.Println(`%s.%s = %s`, varNamePlainStruct, v.fieldName, defaultValue) + out.Printlnf(`%s.%s = %s`, varNamePlainStruct, v.fieldName, defaultValue) out.Indent(-1) - out.Println("}") + out.Printlnf("}") } func (v *defaultValidator) tryDumpDefaultSlice(maxLineLen uint) (string, error) { tmpEmitter := codegen.NewEmitter(maxLineLen) v.defaultValueType.Generate(tmpEmitter) - tmpEmitter.Println("{") + tmpEmitter.Printlnf("{") kind := reflect.ValueOf(v.defaultValue).Kind() switch kind { case reflect.Slice: - for _, value := range v.defaultValue.([]interface{}) { - tmpEmitter.Println("%s,", litter.Sdump(value)) + df, ok := v.defaultValue.([]interface{}) + if !ok { + return "", errors.New("invalid default value") + } + + for _, value := range df { + tmpEmitter.Printlnf("%s,", litter.Sdump(value)) } + default: return "", errors.New("didn't find a slice to dump") } - tmpEmitter.Print("}") + tmpEmitter.Printf("}") + return tmpEmitter.String(), nil } @@ -152,13 +162,16 @@ func (v *arrayValidator) generate(out *codegen.Emitter) { value := fmt.Sprintf("%s.%s", varNamePlainStruct, v.fieldName) fieldName := v.jsonName + var indexes []string + for i := 1; i < v.arrayDepth; i++ { index := fmt.Sprintf("i%d", i) indexes = append(indexes, index) - out.Println(`for %s := range %s {`, index, value) + out.Printlnf(`for %s := range %s {`, index, value) value += fmt.Sprintf("[%s]", index) fieldName += "[%d]" + out.Indent(1) } @@ -168,24 +181,24 @@ func (v *arrayValidator) generate(out *codegen.Emitter) { } if v.minItems != 0 { - out.Println(`if len(%s) < %d {`, value, v.minItems) + out.Printlnf(`if len(%s) < %d {`, value, v.minItems) out.Indent(1) - out.Println(`return fmt.Errorf("field %%s length: must be >= %%d", %s, %d)`, fieldName, v.minItems) + out.Printlnf(`return fmt.Errorf("field %%s length: must be >= %%d", %s, %d)`, fieldName, v.minItems) out.Indent(-1) - out.Println("}") + out.Printlnf("}") } if v.maxItems != 0 { - out.Println(`if len(%s) > %d {`, value, v.maxItems) + out.Printlnf(`if len(%s) > %d {`, value, v.maxItems) out.Indent(1) - out.Println(`return fmt.Errorf("field %%s length: must be <= %%d", %s, %d)`, fieldName, v.maxItems) + out.Printlnf(`return fmt.Errorf("field %%s length: must be <= %%d", %s, %d)`, fieldName, v.maxItems) out.Indent(-1) - out.Println("}") + out.Printlnf("}") } for i := 1; i < v.arrayDepth; i++ { out.Indent(-1) - out.Println("}") + out.Printlnf("}") } } diff --git a/pkg/schemas/model.go b/pkg/schemas/model.go index 190733a6..82c0d248 100644 --- a/pkg/schemas/model.go +++ b/pkg/schemas/model.go @@ -1,8 +1,8 @@ -// Package schema defines JSON schema types. +// Package schemas defines JSON schema types. // // Code borrowed from https://github.com/alecthomas/jsonschema/ // -// Copyright (C) 2014 Alec Thomas +// # Copyright (C) 2014 Alec Thomas // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -25,35 +25,51 @@ package schemas import ( "encoding/json" + "fmt" ) // Schema is the root schema. type Schema struct { *ObjectAsType - ID string `json:"$id"` // RFC draft-wright-json-schema-01, section-9.2 - LegacyID string `json:"id"` // RFC draft-wright-json-schema-00, section 4.5 - Definitions Definitions `json:"definitions,omitempty"` + ID string `json:"$id"` // RFC draft-wright-json-schema-01, section-9.2. + LegacyID string `json:"id"` // RFC draft-wright-json-schema-00, section 4.5. + Definitions Definitions `json:"$defs,omitempty"` } -// UnmarshalJSON implements json.Unmarshaler for Schema struct +// UnmarshalJSON implements json.Unmarshaler for Schema struct. func (s *Schema) UnmarshalJSON(data []byte) error { var unmarshSchema unmarshalerSchema if err := json.Unmarshal(data, &unmarshSchema); err != nil { - return err + return fmt.Errorf("failed to unmarshal schema: %w", err) } - // fall back to id if $id is not present + // Fall back to id if $id is not present. if unmarshSchema.ID == "" { unmarshSchema.ID = unmarshSchema.LegacyID } + // Take care of legacy fields. + var legacySchema struct { + Definitions Definitions `json:"definitions,omitempty"` + } + + if err := json.Unmarshal(data, &legacySchema); err != nil { + return fmt.Errorf("failed to unmarshal schema: %w", err) + } + + if unmarshSchema.Definitions == nil && legacySchema.Definitions != nil { + unmarshSchema.Definitions = legacySchema.Definitions + } + *s = Schema(unmarshSchema) return nil } -type unmarshalerSchema Schema -type ObjectAsType Type +type ( + unmarshalerSchema Schema + ObjectAsType Type +) // TypeList is a list of type names. type TypeList []string @@ -63,74 +79,81 @@ func (t *TypeList) UnmarshalJSON(b []byte) error { if len(b) > 0 && b[0] == '[' { var s []string if err := json.Unmarshal(b, &s); err != nil { - return err + return fmt.Errorf("failed to unmarshal type list: %w", err) } - *t = TypeList(s) + + *t = s + return nil } var s string if err := json.Unmarshal(b, &s); err != nil { - return err + return fmt.Errorf("failed to unmarshal type list: %w", err) } + if s != "" { - *t = TypeList([]string{s}) + *t = []string{s} } else { *t = nil } + return nil } // Definitions hold schema definitions. // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 -// RFC draft-wright-json-schema-validation-00, section 5.26 +// RFC draft-wright-json-schema-validation-00, section 5.26. type Definitions map[string]*Type // Type represents a JSON Schema object type. type Type struct { - // RFC draft-wright-json-schema-00 - Version string `json:"$schema,omitempty"` // section 6.1 - Ref string `json:"$ref,omitempty"` // section 7 - // RFC draft-wright-json-schema-validation-00, section 5 - MultipleOf int `json:"multipleOf,omitempty"` // section 5.1 - Maximum float64 `json:"maximum,omitempty"` // section 5.2 - ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` // section 5.3 - Minimum float64 `json:"minimum,omitempty"` // section 5.4 - ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` // section 5.5 - MaxLength int `json:"maxLength,omitempty"` // section 5.6 - MinLength int `json:"minLength,omitempty"` // section 5.7 - Pattern string `json:"pattern,omitempty"` // section 5.8 - AdditionalItems *Type `json:"additionalItems,omitempty"` // section 5.9 - Items *Type `json:"items,omitempty"` // section 5.9 - MaxItems int `json:"maxItems,omitempty"` // section 5.10 - MinItems int `json:"minItems,omitempty"` // section 5.11 - UniqueItems bool `json:"uniqueItems,omitempty"` // section 5.12 - MaxProperties int `json:"maxProperties,omitempty"` // section 5.13 - MinProperties int `json:"minProperties,omitempty"` // section 5.14 - Required []string `json:"required,omitempty"` // section 5.15 - 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 - Dependencies map[string]*Type `json:"dependencies,omitempty"` // section 5.19 - Enum []interface{} `json:"enum,omitempty"` // section 5.20 - Type TypeList `json:"type,omitempty"` // section 5.21 - AllOf []*Type `json:"allOf,omitempty"` // section 5.22 - AnyOf []*Type `json:"anyOf,omitempty"` // section 5.23 - OneOf []*Type `json:"oneOf,omitempty"` // section 5.24 - Not *Type `json:"not,omitempty"` // section 5.25 - Definitions Definitions `json:"definitions,omitempty"` // section 5.26 - // 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 - // 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 + // RFC draft-wright-json-schema-00. + Version string `json:"$schema,omitempty"` // Section 6.1. + Ref string `json:"$ref,omitempty"` // Section 7. + // RFC draft-wright-json-schema-validation-00, section 5. + MultipleOf int `json:"multipleOf,omitempty"` // Section 5.1. + Maximum float64 `json:"maximum,omitempty"` // Section 5.2. + ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` // Section 5.3. + Minimum float64 `json:"minimum,omitempty"` // Section 5.4. + ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` // Section 5.5. + MaxLength int `json:"maxLength,omitempty"` // Section 5.6. + MinLength int `json:"minLength,omitempty"` // Section 5.7. + Pattern string `json:"pattern,omitempty"` // Section 5.8. + AdditionalItems *Type `json:"additionalItems,omitempty"` // Section 5.9. + Items *Type `json:"items,omitempty"` // Section 5.9. + MaxItems int `json:"maxItems,omitempty"` // Section 5.10. + MinItems int `json:"minItems,omitempty"` // Section 5.11. + UniqueItems bool `json:"uniqueItems,omitempty"` // Section 5.12. + MaxProperties int `json:"maxProperties,omitempty"` // Section 5.13. + MinProperties int `json:"minProperties,omitempty"` // Section 5.14. + Required []string `json:"required,omitempty"` // Section 5.15. + 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. + Type TypeList `json:"type,omitempty"` // Section 5.21. + AllOf []*Type `json:"allOf,omitempty"` // Section 5.22. + AnyOf []*Type `json:"anyOf,omitempty"` // Section 5.23. + OneOf []*Type `json:"oneOf,omitempty"` // Section 5.24. + Not *Type `json:"not,omitempty"` // Section 5.25. + // 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. + // 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. + // RFC draft-handrews-json-schema-validation-02, section 6. + DependentRequired map[string][]string `json:"dependentRequired,omitempty"` // Section 6.5.4. + // RFC draft-handrews-json-schema-validation-02, appendix A. + Definitions Definitions `json:"$defs,omitempty"` + DependentSchemas map[string]*Type `json:"dependentSchemas,omitempty"` // ExtGoCustomType is the name of a (qualified or not) custom Go type // to use for the field. - GoJSONSchemaExtension *GoJSONSchemaExtension `json:"goJSONSchema,omitempty"` + GoJSONSchemaExtension *GoJSONSchemaExtension `json:"goJSONSchema,omitempty"` //nolint:tagliatelle // breaking change } // UnmarshalJSON accepts booleans as schemas where `true` is equivalent to `{}` @@ -143,12 +166,31 @@ func (value *Type) UnmarshalJSON(raw []byte) error { } else { *value = Type{Not: &Type{}} } + return nil } var obj ObjectAsType if err := json.Unmarshal(raw, &obj); err != nil { - return err + return fmt.Errorf("failed to unmarshal type: %w", err) + } + + // Take care of legacy fields from older RFC versions. + legacyObj := struct { + // RFC draft-wright-json-schema-validation-00, section 5. + Dependencies map[string]*Type `json:"dependencies,omitempty"` + Definitions Definitions `json:"definitions,omitempty"` // Section 5.26. + }{} + if err := json.Unmarshal(raw, &legacyObj); err != nil { + return fmt.Errorf("failed to unmarshal type: %w", err) + } + + if legacyObj.Definitions != nil && obj.Definitions == nil { + obj.Definitions = legacyObj.Definitions + } + + if legacyObj.Dependencies != nil && obj.DependentSchemas == nil { + obj.DependentSchemas = legacyObj.Dependencies } *value = Type(obj) diff --git a/pkg/schemas/parse.go b/pkg/schemas/parse.go index 8a123404..9dabca37 100644 --- a/pkg/schemas/parse.go +++ b/pkg/schemas/parse.go @@ -1,6 +1,7 @@ package schemas import ( + "context" "encoding/json" "fmt" "io" @@ -9,57 +10,68 @@ import ( "os" "path/filepath" - "gopkg.in/yaml.v2" - "github.com/atombender/go-jsonschema/pkg/yamlutils" + "github.com/goccy/go-yaml" ) +var errInvalidSchemaRef = fmt.Errorf("schema reference must a file name or HTTP URL") + func FromJSONFile(fileName string) (*Schema, error) { f, err := os.Open(fileName) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to open file: %w", err) } + defer func() { _ = f.Close() }() + return FromJSONReader(f) } func FromJSONReader(r io.Reader) (*Schema, error) { var schema Schema if err := json.NewDecoder(r).Decode(&schema); err != nil { - return nil, err + return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) } + return &schema, nil } func FromYAMLFile(fileName string) (*Schema, error) { f, err := os.Open(fileName) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to open file: %w", err) } + defer func() { _ = f.Close() }() + return FromYAMLReader(f) } func FromYAMLReader(r io.Reader) (*Schema, error) { - // Marshal to JSON first because YAML decoder doesn't understand JSON tags + // Marshal to JSON first because YAML decoder doesn't understand JSON tags. var m map[string]interface{} + if err := yaml.NewDecoder(r).Decode(&m); err != nil { - return nil, err + return nil, fmt.Errorf("failed to unmarshal YAML: %w", err) } + yamlutils.FixMapKeys(m) b, err := json.Marshal(m) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to marshal JSON: %w", err) } + var schema Schema + if err = json.Unmarshal(b, &schema); err != nil { - return nil, err + return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) } + return &schema, nil } @@ -70,20 +82,26 @@ type Loader struct { func (l *Loader) Load(fromURL string) (io.ReadCloser, error) { u, err := url.Parse(fromURL) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse url: %w", err) } if u.Scheme == "http" || u.Scheme == "https" { - resp, err := http.Get(fromURL) + resp, err := http.NewRequestWithContext(context.Background(), http.MethodGet, fromURL, nil) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create request: %w", err) } + return resp.Body, nil } if (u.Scheme == "" || u.Scheme == "file") && u.Host == "" && u.Path != "" { - return os.Open(filepath.Join(l.workingDir, u.Path)) + rc, err := os.Open(filepath.Join(l.workingDir, u.Path)) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + + return rc, nil } - return nil, fmt.Errorf("schema reference must a file name or HTTP URL: %q", fromURL) + return nil, fmt.Errorf("%w: %q", errInvalidSchemaRef, fromURL) } diff --git a/pkg/schemas/types.go b/pkg/schemas/types.go index 37b7f3be..84de9bd0 100644 --- a/pkg/schemas/types.go +++ b/pkg/schemas/types.go @@ -14,6 +14,7 @@ func IsPrimitiveType(t string) bool { switch t { case TypeNameString, TypeNameNumber, TypeNameInteger, TypeNameBoolean, TypeNameNull: return true + default: return false } diff --git a/pkg/yamlutils/yaml.go b/pkg/yamlutils/yaml.go index 52aed442..b121ee65 100644 --- a/pkg/yamlutils/yaml.go +++ b/pkg/yamlutils/yaml.go @@ -1,26 +1,38 @@ package yamlutils -// Fix non-string keys that occur in nested YAML unmarshaling results. +import "fmt" + +// FixMapKeys fixes non-string keys that occur in nested YAML unmarshalling results. func FixMapKeys(m map[string]interface{}) { for k, v := range m { m[k] = fixMapKeysIn(v) } } -// Fix non-string keys that occur in nested YAML unmarshaling results. +// Fix non-string keys that occur in nested YAML unmarshalling results. func fixMapKeysIn(value interface{}) interface{} { switch t := value.(type) { case []interface{}: for i, elem := range t { t[i] = fixMapKeysIn(elem) } + return t + case map[interface{}]interface{}: m := map[string]interface{}{} + for k, v := range t { - m[k.(string)] = fixMapKeysIn(v) + ks, ok := k.(string) + if !ok { + ks = fmt.Sprintf("%v", k) + } + + m[ks] = fixMapKeysIn(v) } + return m + default: return value } diff --git a/tests/data/core/nullableType.go.output b/tests/data/core/nullableType.go.output index f64c71ff..371a1d2f 100644 --- a/tests/data/core/nullableType.go.output +++ b/tests/data/core/nullableType.go.output @@ -9,6 +9,9 @@ type FloatThing *float64 type IntegerThing *int type NullableType struct { + // MyInlineStringValue corresponds to the JSON schema field "MyInlineStringValue". + MyInlineStringValue *string `json:"MyInlineStringValue,omitempty" yaml:"MyInlineStringValue,omitempty"` + // MyStringValue corresponds to the JSON schema field "MyStringValue". MyStringValue StringThing `json:"MyStringValue,omitempty" yaml:"MyStringValue,omitempty"` } diff --git a/tests/data/core/nullableType.json b/tests/data/core/nullableType.json index 307510b6..a715dad4 100644 --- a/tests/data/core/nullableType.json +++ b/tests/data/core/nullableType.json @@ -2,36 +2,41 @@ "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://example.com/nullableType", "type": "object", - "definitions": { + "$defs": { "StringThing": { "type": [ - "null", - "string" + "null", + "string" ] }, "FloatThing": { "type": [ - "number", - "null" + "number", + "null" ] }, "IntegerThing": { "type": [ - "integer", - "null" + "integer", + "null" ] }, "BoolThing": { "type": [ - "boolean", - "null" + "boolean", + "null" ] } }, "properties": { "MyStringValue": { - "$ref": "#/Definitions/StringThing" + "$ref": "#/$defs/StringThing" + }, + "MyInlineStringValue": { + "type": [ + "null", + "string" + ] } } } - diff --git a/tests/data/core/ref.json b/tests/data/core/ref.json index 616e4304..665a884d 100644 --- a/tests/data/core/ref.json +++ b/tests/data/core/ref.json @@ -4,13 +4,13 @@ "type": "object", "properties": { "myThing": { - "$ref": "#/Definitions/Thing" + "$ref": "#/$defs/Thing" }, "myThing2": { - "$ref": "#/Definitions/Thing" + "$ref": "#/$defs/Thing" } }, - "definitions": { + "$defs": { "Thing": { "type": "object", "properties": { @@ -20,4 +20,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/data/core/refExternalFile.json b/tests/data/core/refExternalFile.json index 9db5e4cd..9f61e29c 100644 --- a/tests/data/core/refExternalFile.json +++ b/tests/data/core/refExternalFile.json @@ -4,10 +4,10 @@ "type": "object", "properties": { "myExternalThing": { - "$ref": "./ref.json#/Definitions/Thing" + "$ref": "./ref.json#/$defs/Thing" }, "someOtherExternalThing": { - "$ref": "./ref.json#/Definitions/Thing" + "$ref": "./ref.json#/$defs/Thing" } } -} +} \ No newline at end of file diff --git a/tests/data/core/refExternalFileWithDupe.json b/tests/data/core/refExternalFileWithDupe.json index 060ba69f..e2d7c54e 100644 --- a/tests/data/core/refExternalFileWithDupe.json +++ b/tests/data/core/refExternalFileWithDupe.json @@ -4,13 +4,13 @@ "type": "object", "properties": { "myExternalThing": { - "$ref": "./ref.json#/Definitions/Thing" + "$ref": "./ref.json#/$defs/Thing" }, "myThing": { - "$ref": "#/Definitions/Thing" + "$ref": "#/$defs/Thing" } }, - "definitions": { + "$defs": { "Thing": { "type": "object", "properties": { @@ -20,4 +20,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/data/core/refOld.go.output b/tests/data/core/refOld.go.output new file mode 100644 index 00000000..9b917a83 --- /dev/null +++ b/tests/data/core/refOld.go.output @@ -0,0 +1,16 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package test + +type RefOld struct { + // MyThing corresponds to the JSON schema field "myThing". + MyThing *Thing `json:"myThing,omitempty" yaml:"myThing,omitempty"` + + // MyThing2 corresponds to the JSON schema field "myThing2". + MyThing2 *Thing `json:"myThing2,omitempty" yaml:"myThing2,omitempty"` +} + +type Thing struct { + // Name corresponds to the JSON schema field "name". + Name *string `json:"name,omitempty" yaml:"name,omitempty"` +} diff --git a/tests/data/core/refOld.json b/tests/data/core/refOld.json new file mode 100644 index 00000000..f0023483 --- /dev/null +++ b/tests/data/core/refOld.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://example.com/ref", + "type": "object", + "properties": { + "myThing": { + "$ref": "#/definitions/Thing" + }, + "myThing2": { + "$ref": "#/definitions/Thing" + } + }, + "definitions": { + "Thing": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/tests/data/core/refToEnum.json b/tests/data/core/refToEnum.json index d8bcd043..12bfcbd8 100644 --- a/tests/data/core/refToEnum.json +++ b/tests/data/core/refToEnum.json @@ -4,13 +4,16 @@ "type": "object", "properties": { "myThing": { - "$ref": "#/definitions/Thing" + "$ref": "#/$defs/Thing" } }, - "definitions": { + "$defs": { "Thing": { "type": "string", - "enum": ["x", "y"] + "enum": [ + "x", + "y" + ] } } -} +} \ No newline at end of file diff --git a/tests/data/core/refToPrimitiveString.json b/tests/data/core/refToPrimitiveString.json index e6a68b17..2655bf32 100644 --- a/tests/data/core/refToPrimitiveString.json +++ b/tests/data/core/refToPrimitiveString.json @@ -4,12 +4,12 @@ "type": "object", "properties": { "myThing": { - "$ref": "#/definitions/Thing" + "$ref": "#/$defs/Thing" } }, - "definitions": { + "$defs": { "Thing": { "type": "string" } } -} +} \ No newline at end of file diff --git a/tests/data/crossPackage/other.json b/tests/data/crossPackage/other.json index 40723d3a..2982390c 100644 --- a/tests/data/crossPackage/other.json +++ b/tests/data/crossPackage/other.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://example.com/other", - "definitions": { + "$defs": { "Thing": { "type": "object", "properties": { @@ -11,4 +11,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/data/crossPackage/schema.json b/tests/data/crossPackage/schema.json index 80232094..8fe7878f 100644 --- a/tests/data/crossPackage/schema.json +++ b/tests/data/crossPackage/schema.json @@ -4,13 +4,13 @@ "type": "object", "properties": { "defInSameSchema": { - "$ref": "#/Definitions/Thing" + "$ref": "#/$defs/Thing" }, "defInOtherSchema": { - "$ref": "other.json#/Definitions/Thing" + "$ref": "other.json#/$defs/Thing" } }, - "definitions": { + "$defs": { "Thing": { "type": "object", "properties": { @@ -20,4 +20,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/data/crossPackageNoOutput/other.json b/tests/data/crossPackageNoOutput/other.json index 40723d3a..2982390c 100644 --- a/tests/data/crossPackageNoOutput/other.json +++ b/tests/data/crossPackageNoOutput/other.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://example.com/other", - "definitions": { + "$defs": { "Thing": { "type": "object", "properties": { @@ -11,4 +11,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/data/crossPackageNoOutput/schema.json b/tests/data/crossPackageNoOutput/schema.json index 80232094..8fe7878f 100644 --- a/tests/data/crossPackageNoOutput/schema.json +++ b/tests/data/crossPackageNoOutput/schema.json @@ -4,13 +4,13 @@ "type": "object", "properties": { "defInSameSchema": { - "$ref": "#/Definitions/Thing" + "$ref": "#/$defs/Thing" }, "defInOtherSchema": { - "$ref": "other.json#/Definitions/Thing" + "$ref": "other.json#/$defs/Thing" } }, - "definitions": { + "$defs": { "Thing": { "type": "object", "properties": { @@ -20,4 +20,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/data/miscWithDefaults/cyclic.json b/tests/data/miscWithDefaults/cyclic.json index 7f38e0f6..fa5b4b66 100644 --- a/tests/data/miscWithDefaults/cyclic.json +++ b/tests/data/miscWithDefaults/cyclic.json @@ -4,15 +4,15 @@ "type": "object", "properties": { "a": { - "$ref": "#/definitions/Foo" + "$ref": "#/$defs/Foo" } }, - "definitions": { + "$defs": { "Foo": { "type": "object", "properties": { "refToBar": { - "$ref": "#/definitions/Bar" + "$ref": "#/$defs/Bar" } } }, @@ -20,9 +20,9 @@ "type": "object", "properties": { "refToFoo": { - "$ref": "#/definitions/Foo" + "$ref": "#/$defs/Foo" } } } } -} +} \ No newline at end of file diff --git a/tests/data/miscWithDefaults/cyclicAndRequired1.json b/tests/data/miscWithDefaults/cyclicAndRequired1.json index 25d12e57..8daef895 100644 --- a/tests/data/miscWithDefaults/cyclicAndRequired1.json +++ b/tests/data/miscWithDefaults/cyclicAndRequired1.json @@ -4,16 +4,18 @@ "type": "object", "properties": { "a": { - "$ref": "#/definitions/Foo" + "$ref": "#/$defs/Foo" } }, - "definitions": { + "$defs": { "Foo": { "type": "object", - "required": ["refToBar"], + "required": [ + "refToBar" + ], "properties": { "refToBar": { - "$ref": "#/definitions/Bar" + "$ref": "#/$defs/Bar" } } }, @@ -21,9 +23,9 @@ "type": "object", "properties": { "refToFoo": { - "$ref": "#/definitions/Foo" + "$ref": "#/$defs/Foo" } } } } -} +} \ No newline at end of file diff --git a/tests/data/miscWithDefaults/cyclicAndRequired2.json b/tests/data/miscWithDefaults/cyclicAndRequired2.json index 3198dbc4..f5da0b05 100644 --- a/tests/data/miscWithDefaults/cyclicAndRequired2.json +++ b/tests/data/miscWithDefaults/cyclicAndRequired2.json @@ -4,27 +4,31 @@ "type": "object", "properties": { "a": { - "$ref": "#/definitions/Foo" + "$ref": "#/$defs/Foo" } }, - "definitions": { + "$defs": { "Foo": { "type": "object", - "required": ["refToBar"], + "required": [ + "refToBar" + ], "properties": { "refToBar": { - "$ref": "#/definitions/Bar" + "$ref": "#/$defs/Bar" } } }, "Bar": { "type": "object", - "required": ["refToFoo"], + "required": [ + "refToFoo" + ], "properties": { "refToFoo": { - "$ref": "#/definitions/Foo" + "$ref": "#/$defs/Foo" } } } } -} +} \ No newline at end of file diff --git a/tests/data/miscWithDefaults/rootEmptyJustDefinitions.json b/tests/data/miscWithDefaults/rootEmptyJustDefinitions.json index 9c5f3f31..b5a1a0de 100644 --- a/tests/data/miscWithDefaults/rootEmptyJustDefinitions.json +++ b/tests/data/miscWithDefaults/rootEmptyJustDefinitions.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://example.com/empty_def", - "definitions": { + "$defs": { "Thing": { "type": "object", "properties": { @@ -11,4 +11,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/data/validation/6.1.2_enum.go.output b/tests/data/validation/6.1.2_enum.go.output index 3a86daca..e6c585a5 100644 --- a/tests/data/validation/6.1.2_enum.go.output +++ b/tests/data/validation/6.1.2_enum.go.output @@ -132,9 +132,10 @@ func (j *A612EnumMyNullUntypedEnum) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements json.Marshaler. -func (j *A612EnumMyNullUntypedEnum) MarshalJSON() ([]byte, error) { - return json.Marshal(j.Value) +var enumValues_A612EnumMyNumberTypedEnum = []interface{}{ + 1, + 2, + 3, } // UnmarshalJSON implements json.Unmarshaler. @@ -157,6 +158,37 @@ func (j *A612EnumMyNumberTypedEnum) UnmarshalJSON(b []byte) error { return nil } +// MarshalJSON implements json.Marshaler. +func (j *A612EnumMyNullUntypedEnum) MarshalJSON() ([]byte, error) { + return json.Marshal(j.Value) +} + +var enumValues_A612EnumMyNumberUntypedEnum = []interface{}{ + 1, + 2, + 3, +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *A612EnumMyNumberUntypedEnum) UnmarshalJSON(b []byte) error { + var v float64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_A612EnumMyNumberUntypedEnum { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyNumberUntypedEnum, v) + } + *j = A612EnumMyNumberUntypedEnum(v) + return nil +} + // UnmarshalJSON implements json.Unmarshaler. func (j *A612EnumMyNullTypedEnum) UnmarshalJSON(b []byte) error { var v struct { @@ -179,31 +211,37 @@ func (j *A612EnumMyNullTypedEnum) UnmarshalJSON(b []byte) error { return nil } -// MarshalJSON implements json.Marshaler. -func (j *A612EnumMyNullTypedEnum) MarshalJSON() ([]byte, error) { - return json.Marshal(j.Value) +var enumValues_A612EnumMyStringTypedEnum = []interface{}{ + "red", + "blue", + "green", } // UnmarshalJSON implements json.Unmarshaler. -func (j *A612EnumMyNumberUntypedEnum) UnmarshalJSON(b []byte) error { - var v float64 +func (j *A612EnumMyStringTypedEnum) UnmarshalJSON(b []byte) error { + var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool - for _, expected := range enumValues_A612EnumMyNumberUntypedEnum { + for _, expected := range enumValues_A612EnumMyStringTypedEnum { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyNumberUntypedEnum, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyStringTypedEnum, v) } - *j = A612EnumMyNumberUntypedEnum(v) + *j = A612EnumMyStringTypedEnum(v) return nil } +// MarshalJSON implements json.Marshaler. +func (j *A612EnumMyNullTypedEnum) MarshalJSON() ([]byte, error) { + return json.Marshal(j.Value) +} + // UnmarshalJSON implements json.Unmarshaler. func (j *A612EnumMyMixedUntypedEnum) UnmarshalJSON(b []byte) error { var v struct { @@ -232,44 +270,50 @@ func (j *A612EnumMyMixedUntypedEnum) MarshalJSON() ([]byte, error) { } // UnmarshalJSON implements json.Unmarshaler. -func (j *A612EnumMyStringTypedEnum) UnmarshalJSON(b []byte) error { - var v string - if err := json.Unmarshal(b, &v); err != nil { +func (j *A612EnumMyMixedTypeEnum) UnmarshalJSON(b []byte) error { + var v struct { + Value interface{} + } + if err := json.Unmarshal(b, &v.Value); err != nil { return err } var ok bool - for _, expected := range enumValues_A612EnumMyStringTypedEnum { - if reflect.DeepEqual(v, expected) { + for _, expected := range enumValues_A612EnumMyMixedTypeEnum { + if reflect.DeepEqual(v.Value, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyStringTypedEnum, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyMixedTypeEnum, v.Value) } - *j = A612EnumMyStringTypedEnum(v) + *j = A612EnumMyMixedTypeEnum(v) return nil } +var enumValues_A612EnumMyStringUntypedEnum = []interface{}{ + "red", + "blue", + "green", +} + // UnmarshalJSON implements json.Unmarshaler. -func (j *A612EnumMyMixedTypeEnum) UnmarshalJSON(b []byte) error { - var v struct { - Value interface{} - } - if err := json.Unmarshal(b, &v.Value); err != nil { +func (j *A612EnumMyStringUntypedEnum) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool - for _, expected := range enumValues_A612EnumMyMixedTypeEnum { - if reflect.DeepEqual(v.Value, expected) { + for _, expected := range enumValues_A612EnumMyStringUntypedEnum { + if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyMixedTypeEnum, v.Value) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyStringUntypedEnum, v) } - *j = A612EnumMyMixedTypeEnum(v) + *j = A612EnumMyStringUntypedEnum(v) return nil } @@ -337,44 +381,3 @@ func (j *A612EnumMyBooleanTypedEnum) UnmarshalJSON(b []byte) error { *j = A612EnumMyBooleanTypedEnum(v) return nil } - -// UnmarshalJSON implements json.Unmarshaler. -func (j *A612EnumMyStringUntypedEnum) UnmarshalJSON(b []byte) error { - var v string - if err := json.Unmarshal(b, &v); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_A612EnumMyStringUntypedEnum { - if reflect.DeepEqual(v, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_A612EnumMyStringUntypedEnum, v) - } - *j = A612EnumMyStringUntypedEnum(v) - return nil -} - -var enumValues_A612EnumMyNumberTypedEnum = []interface{}{ - 1, - 2, - 3, -} -var enumValues_A612EnumMyNumberUntypedEnum = []interface{}{ - 1, - 2, - 3, -} -var enumValues_A612EnumMyStringTypedEnum = []interface{}{ - "red", - "blue", - "green", -} -var enumValues_A612EnumMyStringUntypedEnum = []interface{}{ - "red", - "blue", - "green", -} diff --git a/tests/data/yaml/yamlMultilineDescriptions.go.output b/tests/data/yaml/yamlMultilineDescriptions.go.output new file mode 100644 index 00000000..273b967b --- /dev/null +++ b/tests/data/yaml/yamlMultilineDescriptions.go.output @@ -0,0 +1,17 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package test + +type YamlMultilineDescriptions struct { + // I'm a multiline description in a literal block. Literal blocks, on the other + // hand, + // should have hard line breaks and may end in a line break, too. + // + // This may look funky when Go code is generated to a specific line width, though. + // + Bar *string `json:"bar,omitempty" yaml:"bar,omitempty"` + + // I'm a multiline description in a folded block. Folded blocks should not have + // hard line breaks after parsing. They should also not end in a line break. + Foo *string `json:"foo,omitempty" yaml:"foo,omitempty"` +} diff --git a/tests/data/yaml/yamlMultilineDescriptions.yaml b/tests/data/yaml/yamlMultilineDescriptions.yaml new file mode 100644 index 00000000..0a63251e --- /dev/null +++ b/tests/data/yaml/yamlMultilineDescriptions.yaml @@ -0,0 +1,19 @@ +"$schema": "http://json-schema.org/draft-04/schema#" +"$id": "https://example.com/yaml_multiline_description" + +title: MyObject +type: object +properties: + foo: + description: > + I'm a multiline description in a folded block. Folded blocks should not + have hard line breaks after parsing. They should also not end in a line break. + type: string + bar: + description: | + I'm a multiline description in a literal block. Literal blocks, on the other hand, + should have hard line breaks and may end in a line break, too. + + This may look funky when Go code is generated to a specific line width, though. + type: string + diff --git a/tests/data/yaml/yamlStructNameFromFile.go.output b/tests/data/yaml/yamlStructNameFromFile.go.output new file mode 100644 index 00000000..4a1f0536 --- /dev/null +++ b/tests/data/yaml/yamlStructNameFromFile.go.output @@ -0,0 +1,8 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package test + +type YamlStructNameFromFile struct { + // Foo corresponds to the JSON schema field "foo". + Foo *string `json:"foo,omitempty" yaml:"foo,omitempty"` +} diff --git a/tests/data/yaml/yamlStructNameFromFile.yaml b/tests/data/yaml/yamlStructNameFromFile.yaml new file mode 100644 index 00000000..5c0ebb0f --- /dev/null +++ b/tests/data/yaml/yamlStructNameFromFile.yaml @@ -0,0 +1,9 @@ +# mySchema.yaml + +"$schema": "http://json-schema.org/draft-04/schema#" +"$id": "https://example.com/yaml_root_type_name" + +type: object +properties: + foo: + type: string diff --git a/tests/integration_test.go b/tests/integration_test.go index 166c772d..65ab4a82 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -1,8 +1,8 @@ -package tests +package tests_test import ( + "errors" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -18,24 +18,33 @@ var basicConfig = generator.Config{ DefaultPackageName: "github.com/example/test", DefaultOutputName: "-", ResolveExtensions: []string{".json", ".yaml"}, + YAMLExtensions: []string{".yaml", ".yml"}, Warner: func(message string) { log.Printf("[from warner] %s", message) }, } func TestCore(t *testing.T) { + t.Parallel() + testExamples(t, basicConfig, "./data/core") } func TestValidation(t *testing.T) { + t.Parallel() + testExamples(t, basicConfig, "./data/validation") } func TestMiscWithDefaults(t *testing.T) { + t.Parallel() + testExamples(t, basicConfig, "./data/miscWithDefaults") } func TestCrossPackage(t *testing.T) { + t.Parallel() + cfg := basicConfig cfg.SchemaMappings = []generator.SchemaMapping{ { @@ -53,6 +62,8 @@ func TestCrossPackage(t *testing.T) { } func TestCrossPackageNoOutput(t *testing.T) { + t.Parallel() + cfg := basicConfig cfg.SchemaMappings = []generator.SchemaMapping{ { @@ -69,18 +80,39 @@ func TestCrossPackageNoOutput(t *testing.T) { } func TestCapitalization(t *testing.T) { + t.Parallel() + cfg := basicConfig cfg.Capitalizations = []string{"ID", "URL", "HtMl"} testExampleFile(t, cfg, "./data/misc/capitalization.json") } func TestBooleanAsSchema(t *testing.T) { + t.Parallel() + cfg := basicConfig testExampleFile(t, cfg, "./data/misc/boolean-as-schema.json") } +func TestYamlStructNameFromFile(t *testing.T) { + t.Parallel() + + cfg := basicConfig + testExampleFile(t, cfg, "./data/yaml/yamlStructNameFromFile.yaml") +} + +func TestYamlMultilineDescriptions(t *testing.T) { + t.Parallel() + + cfg := basicConfig + cfg.YAMLExtensions = []string{"yaml"} + testExampleFile(t, cfg, "./data/yaml/yamlMultilineDescriptions.yaml") +} + func testExamples(t *testing.T, cfg generator.Config, dataDir string) { - fileInfos, err := ioutil.ReadDir(dataDir) + t.Helper() + + fileInfos, err := os.ReadDir(dataDir) if err != nil { t.Fatal(err.Error()) } @@ -98,92 +130,106 @@ func testExamples(t *testing.T, cfg generator.Config, dataDir string) { } func testExampleFile(t *testing.T, cfg generator.Config, fileName string) { + t.Helper() + t.Run(titleFromFileName(fileName), func(t *testing.T) { - generator, err := generator.New(cfg) + t.Parallel() + + g, err := generator.New(cfg) if err != nil { t.Fatal(err) } - if err := generator.DoFile(fileName); err != nil { + if err := g.DoFile(fileName); err != nil { t.Fatal(err) } - if len(generator.Sources()) == 0 { + if len(g.Sources()) == 0 { t.Fatal("Expected sources to contain something") } - for outputName, source := range generator.Sources() { + for outputName, source := range g.Sources() { if outputName == "-" { - outputName = strings.TrimSuffix(filepath.Base(fileName), ".json") + ".go" + outputName = strings.TrimSuffix(filepath.Base(fileName), filepath.Ext(fileName)) + ".go" } outputName += ".output" goldenFileName := filepath.Join(filepath.Dir(fileName), outputName) t.Logf("Using golden data in %s", mustAbs(goldenFileName)) - goldenData, err := ioutil.ReadFile(goldenFileName) + goldenData, err := os.ReadFile(goldenFileName) if err != nil { if !os.IsNotExist(err) { t.Fatal(err) } goldenData = source t.Log("File does not exist; creating it") - if err = ioutil.WriteFile(goldenFileName, goldenData, 0655); err != nil { + if err = os.WriteFile(goldenFileName, goldenData, 0o655); err != nil { t.Fatal(err) } } if diff, ok := diffStrings(t, string(goldenData), string(source)); !ok { - t.Fatal(fmt.Sprintf("Contents different (left is expected, right is actual):\n%s", *diff)) + t.Fatalf("Contents different (left is expected, right is actual):\n%s", *diff) } } }) } func testFailingExampleFile(t *testing.T, cfg generator.Config, fileName string) { + t.Helper() + t.Run(titleFromFileName(fileName), func(t *testing.T) { - generator, err := generator.New(cfg) + g, err := generator.New(cfg) if err != nil { t.Fatal(err) } - if err := generator.DoFile(fileName); err == nil { + if err := g.DoFile(fileName); err == nil { t.Fatal("Expected test to fail") } }) } func diffStrings(t *testing.T, expected, actual string) (*string, bool) { + t.Helper() + if actual == expected { return nil, true } - dir, err := ioutil.TempDir("", "test") + dir, err := os.MkdirTemp("", "test") if err != nil { t.Fatal(err.Error()) } + defer func() { _ = os.RemoveAll(dir) }() - if err := ioutil.WriteFile(fmt.Sprintf("%s/expected", dir), []byte(expected), 0644); err != nil { + if err = os.WriteFile(fmt.Sprintf("%s/expected", dir), []byte(expected), 0o644); err != nil { t.Fatal(err.Error()) } - if err := ioutil.WriteFile(fmt.Sprintf("%s/actual", dir), []byte(actual), 0644); err != nil { + + if err = os.WriteFile(fmt.Sprintf("%s/actual", dir), []byte(actual), 0o644); err != nil { t.Fatal(err.Error()) } out, err := exec.Command("diff", "--side-by-side", fmt.Sprintf("%s/expected", dir), fmt.Sprintf("%s/actual", dir)).Output() - if _, ok := err.(*exec.ExitError); !ok { + + var exitErr *exec.ExitError + if ok := errors.Is(err, exitErr); !ok { t.Fatal(err.Error()) } diff := string(out) + return &diff, false } func titleFromFileName(fileName string) string { relative := mustRel(mustAbs("./data"), mustAbs(fileName)) + return strings.TrimSuffix(relative, ".json") } @@ -192,6 +238,7 @@ func mustRel(base, s string) string { if err != nil { panic(err) } + return result } @@ -200,5 +247,6 @@ func mustAbs(s string) string { if err != nil { panic(err) } + return result }