From d02f45d777b9650bb9f19406ba81fed02c7184cb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 17 Jun 2026 18:47:15 +0200 Subject: [PATCH 1/2] webui: depend on kernelkit/goyang fork instead of an in-tree copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three YANG 1.1 fixes lived as a frozen, one-shot copy of goyang v1.6.3 under internal/goyang (via a local `replace`) — invisible to dependency/CVE tracking and impossible to update. Move them to the maintained kernelkit/goyang fork (v1.6.3-kkit branch) and depend on it through go.mod, pinned by commit hash; drop the in-tree copy. getopt falls out of the dependency set since it was only used by goyang's CLI, not pkg/yang. Signed-off-by: Joachim Wiberg --- src/webui/go.mod | 16 +- src/webui/go.sum | 10 +- .../internal/goyang/.github/dependabot.yml | 15 - .../goyang/.github/linters/.golangci.yml | 53 - .../goyang/.github/linters/.yaml-lint.yml | 59 - .../internal/goyang/.github/workflows/go.yml | 15 - src/webui/internal/goyang/.gitignore | 1 - src/webui/internal/goyang/AUTHORS | 9 - src/webui/internal/goyang/CONTRIBUTING | 25 - src/webui/internal/goyang/CONTRIBUTORS | 15 - src/webui/internal/goyang/Copyright | 14 - src/webui/internal/goyang/LICENSE | 202 - src/webui/internal/goyang/README.md | 54 - src/webui/internal/goyang/go.mod | 12 - src/webui/internal/goyang/go.sum | 8 - .../internal/goyang/pkg/indent/indent.go | 112 - .../internal/goyang/pkg/indent/indent_test.go | 146 - src/webui/internal/goyang/pkg/yang/ast.go | 461 -- .../internal/goyang/pkg/yang/ast_test.go | 538 --- .../internal/goyang/pkg/yang/bgp_test.go | 571 --- .../internal/goyang/pkg/yang/camelcase.go | 94 - .../goyang/pkg/yang/camelcase_test.go | 53 - src/webui/internal/goyang/pkg/yang/doc.go | 48 - src/webui/internal/goyang/pkg/yang/entry.go | 1664 ------- .../internal/goyang/pkg/yang/entry_test.go | 4141 ----------------- src/webui/internal/goyang/pkg/yang/file.go | 167 - .../internal/goyang/pkg/yang/file_test.go | 166 - src/webui/internal/goyang/pkg/yang/find.go | 96 - .../internal/goyang/pkg/yang/find_test.go | 358 -- .../internal/goyang/pkg/yang/identity.go | 192 - .../internal/goyang/pkg/yang/identity_test.go | 802 ---- src/webui/internal/goyang/pkg/yang/lex.go | 522 --- .../internal/goyang/pkg/yang/lex_test.go | 309 -- .../internal/goyang/pkg/yang/marshal_test.go | 832 ---- src/webui/internal/goyang/pkg/yang/modules.go | 466 -- .../internal/goyang/pkg/yang/modules_test.go | 414 -- src/webui/internal/goyang/pkg/yang/node.go | 388 -- .../internal/goyang/pkg/yang/node_test.go | 622 --- src/webui/internal/goyang/pkg/yang/options.go | 59 - src/webui/internal/goyang/pkg/yang/parse.go | 338 -- .../internal/goyang/pkg/yang/parse_test.go | 539 --- .../pkg/yang/testdata/deviate-delete.yang | 89 - .../yang/testdata/deviate-notsupported.yang | 42 - .../pkg/yang/testdata/deviate-replace.yang | 106 - .../goyang/pkg/yang/testdata/deviate.yang | 81 - .../yang/testdata/find-file-test/blue.yang | 0 .../find-file-test/blue@2000-10-10.yang | 0 .../dir/dirdir/red@2022-02-22.yang | 0 .../find-file-test/dir/red@2020-02-02.yang | 0 .../find-file-test/dir/red@2020-02-20.yang | 0 .../testdata/find-file-test/non-standard.name | 0 .../find-file-test/red@2010-10-10.yang | 0 .../find-file-test/red@2222-2-22.yang | 0 src/webui/internal/goyang/pkg/yang/types.go | 425 -- .../internal/goyang/pkg/yang/types_builtin.go | 700 --- .../goyang/pkg/yang/types_builtin_test.go | 915 ---- .../internal/goyang/pkg/yang/types_test.go | 1720 ------- src/webui/internal/goyang/pkg/yang/yang.go | 1101 ----- .../internal/goyang/pkg/yang/yangtype.go | 330 -- .../internal/goyang/pkg/yang/yangtype_test.go | 136 - .../goyang/pkg/yangentry/build_yang.go | 78 - .../goyang/pkg/yangentry/build_yang_test.go | 129 - .../yangentry/testdata/00-valid-module.yang | 6 - .../yangentry/testdata/01-invalid-module.yang | 6 - .../yangentry/testdata/02-valid-import.yang | 6 - .../yangentry/testdata/03-invalid-import.yang | 6 - .../testdata/04-valid-module-one.yang | 6 - .../testdata/04-valid-module-two.yang | 6 - .../yangentry/testdata/05-circular-main.yang | 12 - .../testdata/subdir/circular-sub-one.yang | 9 - .../testdata/subdir/circular-sub-two.yang | 9 - .../yangentry/testdata/subdir/imported.yang | 5 - src/webui/internal/goyang/testdata/aug.yang | 28 - src/webui/internal/goyang/testdata/base.yang | 93 - src/webui/internal/goyang/testdata/other.yang | 30 - src/webui/internal/goyang/testdata/sub.yang | 26 - .../goyang/testdata/subdir/subdir1.yang | 12 - src/webui/internal/goyang/tree.go | 112 - src/webui/internal/goyang/types.go | 135 - src/webui/internal/goyang/yang.go | 214 - .../openconfig/goyang/pkg/yang/yang.go | 12 +- src/webui/vendor/modules.txt | 6 +- 82 files changed, 20 insertions(+), 21177 deletions(-) delete mode 100644 src/webui/internal/goyang/.github/dependabot.yml delete mode 100644 src/webui/internal/goyang/.github/linters/.golangci.yml delete mode 100644 src/webui/internal/goyang/.github/linters/.yaml-lint.yml delete mode 100644 src/webui/internal/goyang/.github/workflows/go.yml delete mode 100644 src/webui/internal/goyang/.gitignore delete mode 100644 src/webui/internal/goyang/AUTHORS delete mode 100644 src/webui/internal/goyang/CONTRIBUTING delete mode 100644 src/webui/internal/goyang/CONTRIBUTORS delete mode 100644 src/webui/internal/goyang/Copyright delete mode 100644 src/webui/internal/goyang/LICENSE delete mode 100644 src/webui/internal/goyang/README.md delete mode 100644 src/webui/internal/goyang/go.mod delete mode 100644 src/webui/internal/goyang/go.sum delete mode 100644 src/webui/internal/goyang/pkg/indent/indent.go delete mode 100644 src/webui/internal/goyang/pkg/indent/indent_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/ast.go delete mode 100644 src/webui/internal/goyang/pkg/yang/ast_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/bgp_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/camelcase.go delete mode 100644 src/webui/internal/goyang/pkg/yang/camelcase_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/doc.go delete mode 100644 src/webui/internal/goyang/pkg/yang/entry.go delete mode 100644 src/webui/internal/goyang/pkg/yang/entry_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/file.go delete mode 100644 src/webui/internal/goyang/pkg/yang/file_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/find.go delete mode 100644 src/webui/internal/goyang/pkg/yang/find_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/identity.go delete mode 100644 src/webui/internal/goyang/pkg/yang/identity_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/lex.go delete mode 100644 src/webui/internal/goyang/pkg/yang/lex_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/marshal_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/modules.go delete mode 100644 src/webui/internal/goyang/pkg/yang/modules_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/node.go delete mode 100644 src/webui/internal/goyang/pkg/yang/node_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/options.go delete mode 100644 src/webui/internal/goyang/pkg/yang/parse.go delete mode 100644 src/webui/internal/goyang/pkg/yang/parse_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/deviate-delete.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/deviate-notsupported.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/deviate-replace.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/deviate.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/blue.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/blue@2000-10-10.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/dirdir/red@2022-02-22.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/red@2020-02-02.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/red@2020-02-20.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/non-standard.name delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/red@2010-10-10.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/testdata/find-file-test/red@2222-2-22.yang delete mode 100644 src/webui/internal/goyang/pkg/yang/types.go delete mode 100644 src/webui/internal/goyang/pkg/yang/types_builtin.go delete mode 100644 src/webui/internal/goyang/pkg/yang/types_builtin_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/types_test.go delete mode 100644 src/webui/internal/goyang/pkg/yang/yang.go delete mode 100644 src/webui/internal/goyang/pkg/yang/yangtype.go delete mode 100644 src/webui/internal/goyang/pkg/yang/yangtype_test.go delete mode 100644 src/webui/internal/goyang/pkg/yangentry/build_yang.go delete mode 100644 src/webui/internal/goyang/pkg/yangentry/build_yang_test.go delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/00-valid-module.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/01-invalid-module.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/02-valid-import.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/03-invalid-import.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-one.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-two.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/05-circular-main.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-one.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-two.yang delete mode 100644 src/webui/internal/goyang/pkg/yangentry/testdata/subdir/imported.yang delete mode 100644 src/webui/internal/goyang/testdata/aug.yang delete mode 100644 src/webui/internal/goyang/testdata/base.yang delete mode 100644 src/webui/internal/goyang/testdata/other.yang delete mode 100644 src/webui/internal/goyang/testdata/sub.yang delete mode 100644 src/webui/internal/goyang/testdata/subdir/subdir1.yang delete mode 100644 src/webui/internal/goyang/tree.go delete mode 100644 src/webui/internal/goyang/types.go delete mode 100644 src/webui/internal/goyang/yang.go diff --git a/src/webui/go.mod b/src/webui/go.mod index 6dbdea367..0dee58857 100644 --- a/src/webui/go.mod +++ b/src/webui/go.mod @@ -4,14 +4,10 @@ go 1.22.0 toolchain go1.22.2 -require ( - github.com/google/go-cmp v0.7.0 // indirect - github.com/openconfig/goyang v1.6.3 // indirect - github.com/pborman/getopt v1.1.0 // indirect -) +require github.com/openconfig/goyang v1.6.3 -// Local fork of goyang with YANG 1.1 fixes: -// - Uses.Augment: *Augment → []*Augment (multiple augments per uses) -// - Value: add Reference field (when { reference "..."; }) -// - Input/Output: add Must field (must statements in rpc input/output) -replace github.com/openconfig/goyang => ./internal/goyang +require github.com/google/go-cmp v0.7.0 // indirect + +// kernelkit/goyang fork carrying our YANG 1.1 fixes: reference on Value, +// multiple uses-augments, and must in rpc input/output. +replace github.com/openconfig/goyang => github.com/kernelkit/goyang v1.6.4-0.20260617163501-afcacf84230c diff --git a/src/webui/go.sum b/src/webui/go.sum index e836eaa6d..502748e59 100644 --- a/src/webui/go.sum +++ b/src/webui/go.sum @@ -1,6 +1,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/openconfig/goyang v1.6.3 h1:9nWXBwd6b4+nZr8ni7O4zUXVhrVMXCLFz8os5YWFuo4= -github.com/openconfig/goyang v1.6.3/go.mod h1:5WolITjek1NF8yrNERyVZ7jqjOClJTpO8p/+OwmETM4= -github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0= -github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= +github.com/kernelkit/goyang v1.6.4-0.20260617163501-afcacf84230c h1:CFApC5asdQoMmQZ1YdP2fDX38K37vObCH8EEKeMFHE8= +github.com/kernelkit/goyang v1.6.4-0.20260617163501-afcacf84230c/go.mod h1:5WolITjek1NF8yrNERyVZ7jqjOClJTpO8p/+OwmETM4= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/openconfig/gnmi v0.14.1 h1:qKMuFvhIRR2/xxCOsStPQ25aKpbMDdWr3kI+nP9bhMs= +github.com/openconfig/gnmi v0.14.1/go.mod h1:whr6zVq9PCU8mV1D0K9v7Ajd3+swoN6Yam9n8OH3eT0= diff --git a/src/webui/internal/goyang/.github/dependabot.yml b/src/webui/internal/goyang/.github/dependabot.yml deleted file mode 100644 index a2a66d097..000000000 --- a/src/webui/internal/goyang/.github/dependabot.yml +++ /dev/null @@ -1,15 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "gomod" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" - - package-ecosystem: "github-actions" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" diff --git a/src/webui/internal/goyang/.github/linters/.golangci.yml b/src/webui/internal/goyang/.github/linters/.golangci.yml deleted file mode 100644 index dca2af2e7..000000000 --- a/src/webui/internal/goyang/.github/linters/.golangci.yml +++ /dev/null @@ -1,53 +0,0 @@ ---- -######################### -######################### -## Golang Linter rules ## -######################### -######################### - -# configure golangci-lint -# see https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml -run: - timeout: 10m -issues: - exclude-rules: - - path: _test\.go - linters: - - dupl - - gosec - - goconst - new: true -linters: - enable: - - gosec - - unconvert - - goconst - - goimports - - gofmt - - gocritic - - govet - - revive - - staticcheck - - unconvert - - unparam - - unused - - wastedassign - - whitespace -linters-settings: - errcheck: - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: true - govet: - # report about shadowed variables - check-shadowing: false - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - gocritic: - disabled-checks: - - singleCaseSwitch - - appendAssign - revive: - ignore-generated-header: true - severity: warning diff --git a/src/webui/internal/goyang/.github/linters/.yaml-lint.yml b/src/webui/internal/goyang/.github/linters/.yaml-lint.yml deleted file mode 100644 index e9ec8bef4..000000000 --- a/src/webui/internal/goyang/.github/linters/.yaml-lint.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -########################################### -# These are the rules used for # -# linting all the yaml files in the stack # -# NOTE: # -# You can disable line with: # -# # yamllint disable-line # -########################################### -rules: - braces: - level: warning - min-spaces-inside: 0 - max-spaces-inside: 0 - min-spaces-inside-empty: 1 - max-spaces-inside-empty: 5 - brackets: - level: warning - min-spaces-inside: 0 - max-spaces-inside: 0 - min-spaces-inside-empty: 1 - max-spaces-inside-empty: 5 - colons: - level: warning - max-spaces-before: 0 - max-spaces-after: 1 - commas: - level: warning - max-spaces-before: 0 - min-spaces-after: 1 - max-spaces-after: 1 - comments: disable - comments-indentation: disable - document-end: disable - document-start: - level: warning - present: true - empty-lines: - level: warning - max: 2 - max-start: 0 - max-end: 0 - hyphens: - level: warning - max-spaces-after: 1 - indentation: - level: warning - spaces: consistent - indent-sequences: true - check-multi-line-strings: false - key-duplicates: enable - line-length: - level: warning - max: 120 - allow-non-breakable-words: true - allow-non-breakable-inline-mappings: true - new-line-at-end-of-file: disable - new-lines: - type: unix - trailing-spaces: disable diff --git a/src/webui/internal/goyang/.github/workflows/go.yml b/src/webui/internal/goyang/.github/workflows/go.yml deleted file mode 100644 index 241920888..000000000 --- a/src/webui/internal/goyang/.github/workflows/go.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Go - -on: - push: - branches: [ master ] - pull_request: - schedule: - - cron: "0 0 * * *" - -jobs: - go: - uses: openconfig/common-ci/.github/workflows/go.yml@125b6b58286d116b216e45c33cb859f547965d61 - - linter: - uses: openconfig/common-ci/.github/workflows/linter.yml@125b6b58286d116b216e45c33cb859f547965d61 diff --git a/src/webui/internal/goyang/.gitignore b/src/webui/internal/goyang/.gitignore deleted file mode 100644 index 6e92f57d4..000000000 --- a/src/webui/internal/goyang/.gitignore +++ /dev/null @@ -1 +0,0 @@ -tags diff --git a/src/webui/internal/goyang/AUTHORS b/src/webui/internal/goyang/AUTHORS deleted file mode 100644 index 121ba4efe..000000000 --- a/src/webui/internal/goyang/AUTHORS +++ /dev/null @@ -1,9 +0,0 @@ -# This is the official list of goyang authors for copyright purposes. -# This file is distinct from the CONTRIBUTORS files. -# See the latter for an explanation. - -# Names should be added to this file as: -# Name or Organization -# The email address is not required for organizations. - -Google Inc. \ No newline at end of file diff --git a/src/webui/internal/goyang/CONTRIBUTING b/src/webui/internal/goyang/CONTRIBUTING deleted file mode 100644 index 574d217e7..000000000 --- a/src/webui/internal/goyang/CONTRIBUTING +++ /dev/null @@ -1,25 +0,0 @@ -Want to contribute? Great! First, read this page (including the small print at the end). - -### Before you contribute -Before we can use your code, you must sign the -[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) -(CLA), which you can do online. The CLA is necessary mainly because you own the -copyright to your changes, even after your contribution becomes part of our -codebase, so we need your permission to use and distribute your code. We also -need to be sure of various other things—for instance that you'll tell us if you -know that your code infringes on other people's patents. You don't have to sign -the CLA until after you've submitted your code for review and a member has -approved it, but you must do it before we can put your code into our codebase. -Before you start working on a larger contribution, you should get in touch with -us first through the issue tracker with your idea so that we can help out and -possibly guide you. Coordinating up front makes it much easier to avoid -frustration later on. - -### Code reviews -All submissions, including submissions by project members, require review. We -use Github pull requests for this purpose. - -### The small print -Contributions made by corporations are covered by a different agreement than -the one above, the -[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). \ No newline at end of file diff --git a/src/webui/internal/goyang/CONTRIBUTORS b/src/webui/internal/goyang/CONTRIBUTORS deleted file mode 100644 index b2ac0e81d..000000000 --- a/src/webui/internal/goyang/CONTRIBUTORS +++ /dev/null @@ -1,15 +0,0 @@ -# People who have agreed to one of the CLAs and can contribute patches. -# The AUTHORS file lists the copyright holders; this file -# lists people. For example, Google employees are listed here -# but not in AUTHORS, because Google holds the copyright. -# -# https://developers.google.com/open-source/cla/individual -# https://developers.google.com/open-source/cla/corporate -# -# Names should be added to this file as: -# Name - -Paul Borman -Andrew Fort -Rob Shakir -Sean Condon diff --git a/src/webui/internal/goyang/Copyright b/src/webui/internal/goyang/Copyright deleted file mode 100644 index 663fa3a0b..000000000 --- a/src/webui/internal/goyang/Copyright +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - diff --git a/src/webui/internal/goyang/LICENSE b/src/webui/internal/goyang/LICENSE deleted file mode 100644 index 8f71f43fe..000000000 --- a/src/webui/internal/goyang/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/src/webui/internal/goyang/README.md b/src/webui/internal/goyang/README.md deleted file mode 100644 index df07ad8f0..000000000 --- a/src/webui/internal/goyang/README.md +++ /dev/null @@ -1,54 +0,0 @@ -![Go](https://github.com/openconfig/goyang/workflows/Go/badge.svg?branch=master) -[![Coverage Status](https://coveralls.io/repos/github/openconfig/goyang/badge.svg?branch=master)](https://coveralls.io/github/openconfig/goyang?branch=master) - -Current support for `goyang` is for the [latest 3 Go releases](https://golang.org/project/#release). - -# goyang -YANG parser and compiler for Go programs. - -The yang package (pkg/yang) is used to convert a YANG schema into either an -in memory abstract syntax trees (ast) or more fully resolved, in memory, "Entry" -trees. An Entry tree consists only of Entry structures and has had -augmentation, imports, and includes all applied. - -goyang is a sample program that uses the yang (pkg/yang) package. - -goyang uses the yang package to create an in-memory tree representation of -schemas defined in YANG and then dumps out the contents in several forms. -The forms include: - -* tree - a simple tree representation -* types - list understood types extracted from the schema - -The yang package, and the goyang program, are not complete and are a work in -progress. - -For more complex output types, such as Go structs, and protobuf messages -please use the [openconfig/ygot](https://github.com/openconfig/ygot) package, -which uses this package as its backend. - -### Getting started - -To build goyang, ensure you have go language tools installed -(available at [golang.org](https://golang.org/dl)) and that the `GOPATH` -environment variable is set to your Go workspace. - -1. `go get github.com/openconfig/goyang` - * This will download goyang code and dependencies into the src -subdirectory in your workspace. - -2. `cd /src/github.com/openconfig/goyang` - -3. `go build` - - * This will build the goyang binary and place it in the bin -subdirectory in your workspace. - -### Contributing to goyang - -goyang is still a work-in-progress and we welcome contributions. Please see -the `CONTRIBUTING` file for information about how to contribute to the codebase. - -### Disclaimer - -This is not an official Google product. diff --git a/src/webui/internal/goyang/go.mod b/src/webui/internal/goyang/go.mod deleted file mode 100644 index 0e7fc6ce5..000000000 --- a/src/webui/internal/goyang/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -module github.com/openconfig/goyang - -go 1.22.0 - -toolchain go1.24.1 - -require ( - github.com/google/go-cmp v0.7.0 - github.com/kylelemons/godebug v1.1.0 - github.com/openconfig/gnmi v0.14.1 - github.com/pborman/getopt v1.1.0 -) diff --git a/src/webui/internal/goyang/go.sum b/src/webui/internal/goyang/go.sum deleted file mode 100644 index 8e9dc6ede..000000000 --- a/src/webui/internal/goyang/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/openconfig/gnmi v0.14.1 h1:qKMuFvhIRR2/xxCOsStPQ25aKpbMDdWr3kI+nP9bhMs= -github.com/openconfig/gnmi v0.14.1/go.mod h1:whr6zVq9PCU8mV1D0K9v7Ajd3+swoN6Yam9n8OH3eT0= -github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0= -github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= diff --git a/src/webui/internal/goyang/pkg/indent/indent.go b/src/webui/internal/goyang/pkg/indent/indent.go deleted file mode 100644 index a67b88856..000000000 --- a/src/webui/internal/goyang/pkg/indent/indent.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package indent indents lines of text. -package indent - -import ( - "bytes" - "io" - "strings" -) - -// String returns s with each line in s prefixed by indent. -func String(indent, s string) string { - if indent == "" || s == "" { - return s - } - lines := strings.SplitAfter(s, "\n") - if len(lines[len(lines)-1]) == 0 { - lines = lines[:len(lines)-1] - } - return strings.Join(append([]string{""}, lines...), indent) -} - -// Bytes returns b with each line in b prefixed by indent. -func Bytes(indent, b []byte) []byte { - if len(indent) == 0 || len(b) == 0 { - return b - } - lines := bytes.SplitAfter(b, []byte{'\n'}) - if len(lines[len(lines)-1]) == 0 { - lines = lines[:len(lines)-1] - } - return bytes.Join(append([][]byte{{}}, lines...), indent) -} - -// NewWriter returns an io.Writer that prefixes the lines written to it with -// indent and then writes them to w. The writer returns the number of bytes -// written to the underlying Writer. -func NewWriter(w io.Writer, indent string) io.Writer { - if indent == "" { - return w - } - return &iw{ - w: w, - prefix: []byte(indent), - } -} - -type iw struct { - w io.Writer - prefix []byte - partial bool // true if next line's indent already written -} - -// Write implements io.Writer. -func (w *iw) Write(buf []byte) (int, error) { - if len(buf) == 0 { - return 0, nil - } - lines := bytes.SplitAfter(buf, []byte{'\n'}) - if len(lines[len(lines)-1]) == 0 { - lines = lines[:len(lines)-1] - } - if !w.partial { - lines = append([][]byte{{}}, lines...) - } - joined := bytes.Join(lines, w.prefix) - w.partial = joined[len(joined)-1] != '\n' - - n, err := w.w.Write(joined) - if err != nil { - return actualWrittenSize(n, len(w.prefix), lines), err - } - - return len(buf), nil -} - -func actualWrittenSize(underlay, prefix int, lines [][]byte) int { - actual := 0 - remain := underlay - for _, line := range lines { - if len(line) == 0 { - continue - } - - addition := remain - prefix - if addition <= 0 { - return actual - } - - if addition <= len(line) { - return actual + addition - } - - actual += len(line) - remain -= prefix + len(line) - } - - return actual -} diff --git a/src/webui/internal/goyang/pkg/indent/indent_test.go b/src/webui/internal/goyang/pkg/indent/indent_test.go deleted file mode 100644 index 3fad0cc91..000000000 --- a/src/webui/internal/goyang/pkg/indent/indent_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package indent - -import ( - "bytes" - "errors" - "testing" -) - -var tests = []struct { - prefix, in, out string -}{ - { - "", "", "", - }, { - "--", "", "", - }, { - "", "x\nx", "x\nx", - }, { - "--", "x", "--x", - }, { - "--", "\n", "--\n", - }, { - "--", "\n\n", "--\n--\n", - }, { - "--", "x\n", "--x\n", - }, { - "--", "\nx", "--\n--x", - }, { - "--", "two\nlines\n", "--two\n--lines\n", - }, { - "--", "\nempty\nfirst\n", "--\n--empty\n--first\n", - }, { - "--", "empty\nlast\n\n", "--empty\n--last\n--\n", - }, { - "--", "empty\n\nmiddle\n", "--empty\n--\n--middle\n", - }, -} - -func TestIndent(t *testing.T) { - for x, tt := range tests { - out := String(tt.prefix, tt.in) - if out != tt.out { - t.Errorf("#%d: got %q, want %q", x, out, tt.out) - } - bout := string(Bytes([]byte(tt.prefix), []byte(tt.in))) - if bout != out { - t.Errorf("#%d: Bytes got %q\n String got %q", x, bout, out) - } - } -} - -func TestWriter(t *testing.T) { -Test: - for x, tt := range tests { - for size := 1; size < 64; size <<= 1 { - var b bytes.Buffer - w := NewWriter(&b, tt.prefix) - data := []byte(tt.in) - for len(data) > size { - if _, err := w.Write(data[:size]); err != nil { - t.Errorf("#%d: %v", x, err) - continue Test - } - data = data[size:] - } - if _, err := w.Write(data); err != nil { - t.Errorf("#%d/%d: %v", x, size, err) - continue Test - } - - out := b.String() - if out != tt.out { - t.Errorf("#%d/%d: got %q, want %q", x, size, out, tt.out) - } - } - } -} - -func TestWrittenSize(t *testing.T) { - for x, tt := range tests { - var b bytes.Buffer - w := NewWriter(&b, tt.prefix) - data := []byte(tt.in) - if n, _ := w.Write(data); n != len(data) { - t.Errorf("#%d: got %d, want %d", x, n, len(data)) - } - } -} - -func TestWrittenSizeWithError(t *testing.T) { - table := []struct { - prefix string - input string - underlay int - expected int - }{ - {"--", "two\nlines\n", 0, 0}, - {"--", "two\nlines\n", 1, 0}, // - - {"--", "two\nlines\n", 2, 0}, // - - {"--", "two\nlines\n", 3, 1}, // t - {"--", "two\nlines\n", 4, 2}, // w - {"--", "two\nlines\n", 5, 3}, // o - {"--", "two\nlines\n", 6, 4}, // \n - {"--", "two\nlines\n", 7, 4}, // - - {"--", "two\nlines\n", 8, 4}, // - - {"--", "two\nlines\n", 9, 5}, // l - {"--", "two\nlines\n", 10, 6}, // i - {"--", "two\nlines\n", 11, 7}, // n - {"--", "two\nlines\n", 12, 8}, // e - {"--", "two\nlines\n", 13, 9}, // s - {"--", "two\nlines\n", 14, 10}, // \n - {"--", "two\nlines\n", 15, 10}, // - - {"--", "two\nlines\n", 16, 10}, // - - } - - for _, d := range table { - uw := errorWriter{d.underlay} - w := NewWriter(uw, d.prefix) - data := []byte(d.input) - if n, _ := w.Write(data); n != d.expected { - t.Errorf("underlay: %d, got %d, want %d, err: ", d.underlay, n, d.expected) - } - } -} - -type errorWriter struct { - ret int -} - -func (w errorWriter) Write(buf []byte) (int, error) { - return w.ret, errors.New("error") -} diff --git a/src/webui/internal/goyang/pkg/yang/ast.go b/src/webui/internal/goyang/pkg/yang/ast.go deleted file mode 100644 index 3c7edb6a4..000000000 --- a/src/webui/internal/goyang/pkg/yang/ast.go +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// This file implements BuildAST() and its associated helper structs and -// functions for constructing an AST of Nodes from a Statement tree. This -// function also populates all typedefs into a type cache. -// -// The initTypes function generates the helper struct and functions that -// recursively fill in the various Node structures defined in yang.go. -// BuildAST() then uses those functions to convert raw parsed Statements into -// an AST. - -import ( - "errors" - "fmt" - "reflect" - "strings" -) - -func init() { - // Initialize the global variables `typeMap` and `nameMap`. - // By doing this, we are making the assumption that all modules will be - // parsed according to the type hierarchy rooted at `meta`, and thus - // all input YANG modules will be parsed in this manner. - initTypes(reflect.TypeOf(&meta{})) -} - -// A yangStatement contains all information needed to build a particular -// type of statement into an AST node. -type yangStatement struct { - // funcs is the map of YANG field names to the function that populates - // the statement into the AST node. - funcs map[string]func(*Statement, reflect.Value, reflect.Value, *typeDictionary) error - // required is a list of fields that must be present in the statement. - required []string - // sRequired maps a statement name to a list of required sub-field - // names. The statement name can be an alias of the primary field type. - // e.g. If a field is required by statement type foo, then only foo - // should have the field. If bar is an alias of foo, it must not - // have this field. - sRequired map[string][]string - // addext is the function to handle possible extensions. - addext func(*Statement, reflect.Value, reflect.Value) error -} - -// newYangStatement creates a new yangStatement. -func newYangStatement() *yangStatement { - return &yangStatement{ - funcs: make(map[string]func(*Statement, reflect.Value, reflect.Value, *typeDictionary) error), - sRequired: make(map[string][]string), - } -} - -var ( - // The following maps are built up at init time. - // typeMap provides a lookup from a Node type to the corresponding - // yangStatement. - typeMap = map[reflect.Type]*yangStatement{} - // nameMap provides a lookup from a keyword string to the corresponding - // concrete type implementing the Node interface (see yang.go). - nameMap = map[string]reflect.Type{} - - // The following are helper types used by the implementation. - statementType = reflect.TypeOf(&Statement{}) - nilValue = reflect.ValueOf(nil) - // nodeType is the reflect.Type of the Node interface. - nodeType = reflect.TypeOf((*Node)(nil)).Elem() -) - -// meta is a collection of top-level statements. There is no actual -// statement named "meta". All other statements are a sub-statement of one -// of the meta statements. -type meta struct { - Module []*Module `yang:"module"` -} - -// aliases is a map of "aliased" names, that is, two types of statements -// that parse (nearly) the same. -// NOTE: This only works for root-level aliasing for now, which is good enough -// for module/submodule. This is because yangStatement.funcs doesn't store the -// handler function for aliased fields, and sRequired also may only store the -// correct values when processing a root-level statement due to aliasing. These -// issues would need to be fixed in order to support aliasing for non-top-level -// statements. -var aliases = map[string]string{ - "submodule": "module", -} - -// buildASTWithTypeDict creates an AST for the input statement, and returns its -// root node. It also takes as input a type dictionary into which any -// encountered typedefs within the statement are cached. -func buildASTWithTypeDict(stmt *Statement, types *typeDictionary) (Node, error) { - v, err := build(stmt, nilValue, types) - if err != nil { - return nil, err - } - return v.Interface().(Node), nil -} - -// build builds and returns an AST from the statement stmt and with parent node -// parent. It also takes as input a type dictionary types into which any -// encountered typedefs within the statement are cached. The type of value -// returned depends on the keyword in stmt (see yang.go). It returns an error -// if it cannot build the statement into its corresponding Node type. -func build(stmt *Statement, parent reflect.Value, types *typeDictionary) (v reflect.Value, err error) { - defer func() { - // If we are returning a real Node then call addTypedefs - // if the node possibly contains typedefs. - // Cache these in the typedef cache for look-ups. - if err != nil || v == nilValue { - return - } - if t, ok := v.Interface().(Typedefer); ok { - types.addTypedefs(t) - } - }() - keyword := stmt.Keyword - if k, ok := aliases[stmt.Keyword]; ok { - keyword = k - } - t := nameMap[keyword] - y := typeMap[t] - // Keep track of which substatements are present in the statement. - found := map[string]bool{} - - // Get the struct type we are pointing to. - t = t.Elem() - // v is a pointer to the instantiated structure we are building. - v = reflect.New(t) - - // Handle special cases that are not actually substatements: - - if fn := y.funcs["Name"]; fn != nil { - // Name uses stmt directly. - if err := fn(stmt, v, parent, types); err != nil { - return nilValue, err - } - } - if fn := y.funcs["Statement"]; fn != nil { - // Statement uses stmt directly. - if err := fn(stmt, v, parent, types); err != nil { - return nilValue, err - } - } - if fn := y.funcs["Parent"]; fn != nil { - // parent is the parent node, which is nilValue (reflect.ValueOf(nil)) if there is none. - // parent.IsValid will return false when parent is a nil interface - // parent.IsValid will true if parent references a concrete type - // (even if it is nil). - if parent.IsValid() { - if err := fn(stmt, v, parent, types); err != nil { - return nilValue, err - } - } - } - - // Now handle the substatements - - for _, ss := range stmt.statements { - found[ss.Keyword] = true - fn := y.funcs[ss.Keyword] - switch { - case fn != nil: - // Normal case, the keyword is known. - if err := fn(ss, v, parent, types); err != nil { - return nilValue, err - } - case len(strings.Split(ss.Keyword, ":")) == 2: - // Keyword is not known but it has a prefix so it might - // be an extension. - if y.addext == nil { - return nilValue, fmt.Errorf("%s: no extension function", ss.Location()) - } - y.addext(ss, v, parent) - default: - return nilValue, fmt.Errorf("%s: unknown %s field: %s", ss.Location(), stmt.Keyword, ss.Keyword) - } - } - - // Make sure all of our required field are there. - for _, r := range y.required { - if !found[r] { - return nilValue, fmt.Errorf("%s: missing required %s field: %s", stmt.Location(), stmt.Keyword, r) - } - } - - // Make sure required fields based on our keyword are there (module vs submodule) - for _, r := range y.sRequired[stmt.Keyword] { - if !found[r] { - return nilValue, fmt.Errorf("%s: missing required %s field: %s", stmt.Location(), stmt.Keyword, r) - } - } - - // Make sure we don't have any field set that is required by a different keyword. - for n, or := range y.sRequired { - if n == stmt.Keyword { - continue - } - for _, r := range or { - if found[r] { - return nilValue, fmt.Errorf("%s: unknown %s field: %s", stmt.Location(), stmt.Keyword, r) - } - } - } - return v, nil -} - -// initTypes creates the functions necessary to build a Statement into the -// given the type "at" based on its possible substatements. at must implement -// Node, with its concrete type being a pointer to a struct defined in yang.go. -// -// This function also builds up the functions to populate the input type -// dictionary types with any encountered typedefs within the statement. -// -// For each field of the struct with a yang tag (e.g., `yang:"command"`), a -// function is created with "command" as its unique ID. The complete map of -// builder functions for at is then added to the typeMap map with at as the -// key. The idea is to call these builder functions for each substatement -// encountered. -// -// The functions have the form: -// -// func fn(ss *Statement, v, p reflect.Value, types *typeDictionary) error -// -// Given stmt as a Statement of type at, ss is a substatement of stmt (in a few -// exceptional cases, ss is the Statement itself). v must have the same type -// as at and is the structure being filled in. p is the parent Node, or nil. -// types is the type dictionary cache of the current set of modules being parsed, -// which is used for looking up typedefs. p is only used to set the Parent -// field of a Node. For example, given the following structure and variables: -// -// type Include struct { -// Name string `yang:"Name"` -// Source *Statement `yang:"Statement"` -// Parent Node `yang:"Parent"` -// Extensions []*Statement `yang:"Ext"` -// RevisionDate *Value `yang:"revision-date"` -// } -// -// var inc = &Include{} -// var vInc = reflect.ValueOf(inc) -// var tInc = reflect.TypeOf(inc) -// -// Functions are created for each fields and named Name, Statement, Parent, Ext, -// and revision-date. -// -// The function built for RevisionDate will be called for any substatement, -// ds, of stmt that has the keyword "revision-date" along with the value of -// vInc and its parent: -// -// typeMap[tInc]["revision-date"](ss, vInc, parent, types) -// -// Normal fields are all processed this same way. -// -// The other 4 fields are special. In the case of Name, Statement, and Parent, -// the function is passed stmt, rather than ss, as these fields are not filled in -// by substatements. -// -// The Name command must set its field to the Statement's argument. The -// Statement command must set its field to the Statement itself. The -// Parent command must set its field with the Node of its parent (the -// parent parameter). -// -// The Ext command is unique and must decode into a []*Statement. This is a -// slice of all statements that use unknown keywords with a prefix (in a valid -// .yang file these should be the extensions). -// -// The Field can have attributes delimited by a ','. The only -// supported attributes are: -// -// nomerge: Do not merge this field -// required: This field must be populated -// required=KIND: This field must be populated if the keyword is KIND -// otherwise this field must not be present. -// (This is to support merging Module and SubModule). -// -// If at contains substructures, initTypes recurses on the substructures. -func initTypes(at reflect.Type) { - if at.Kind() != reflect.Ptr || at.Elem().Kind() != reflect.Struct { - panic(fmt.Sprintf("interface not a struct pointer, is %v", at)) - } - if typeMap[at] != nil { - return // we already defined this type - } - - y := newYangStatement() - typeMap[at] = y - t := at.Elem() - for i := 0; i != t.NumField(); i++ { - i := i - f := t.Field(i) - yang := f.Tag.Get("yang") - if yang == "" { - continue - } - parts := strings.Split(yang, ",") - name := parts[0] - if a, ok := aliases[name]; ok { - name = a - } - - const reqe = "required=" - for _, p := range parts[1:] { - switch { - case p == "nomerge": - case p == "required": - y.required = append(y.required, name) - case strings.HasPrefix(p, reqe): - p = p[len(reqe):] - y.sRequired[p] = append(y.sRequired[p], name) - default: - panic(f.Name + ": unknown tag: " + p) - } - } - - // Ext means this is where we squirrel away extensions - if name == "Ext" { - // stmt is the extension to put into v at for field f. - y.addext = func(stmt *Statement, v, _ reflect.Value) error { - if v.Type() != at { - panic(fmt.Sprintf("given type %s, need type %s", v.Type(), at)) - } - fv := v.Elem().Field(i) - fv.Set(reflect.Append(fv, reflect.ValueOf(stmt))) - return nil - } - continue - } - - // descend runs initType on dt if it has not already done so. - descend := func(name string, dt reflect.Type) { - switch nameMap[name] { - case nil: - nameMap[name] = dt - initTypes(dt) // Make sure that structure type is included - case dt: - default: - panic("redeclared type " + name) - } - } - - // Create a function, fn, that will build the field from a - // Statement. These functions are used when actually making - // an AST from a Statement Tree. - var fn func(*Statement, reflect.Value, reflect.Value, *typeDictionary) error - - // The field can be a pointer, a slice or a string - switch f.Type.Kind() { - default: - panic(fmt.Sprintf("invalid type: %v", f.Type.Kind())) - - case reflect.Interface: - // The only case of this should be the "Parent" field. - if name != "Parent" { - panic(fmt.Sprintf("interface field is %s, not Parent", name)) - } - fn = func(stmt *Statement, v, p reflect.Value, types *typeDictionary) error { - if !p.Type().Implements(nodeType) { - panic(fmt.Sprintf("invalid interface: %v", f.Type.Kind())) - } - v.Elem().Field(i).Set(p) - return nil - } - case reflect.String: - // The only case of this should be the "Name" field - if name != "Name" { - panic(fmt.Sprintf("string field is %s, not Name", name)) - } - fn = func(stmt *Statement, v, _ reflect.Value, types *typeDictionary) error { - if v.Type() != at { - panic(fmt.Sprintf("got type %v, want %v", v.Type(), at)) - } - fv := v.Elem().Field(i) - if fv.String() != "" { - return errors.New(stmt.Keyword + ": already set") - } - - v.Elem().Field(i).SetString(stmt.Argument) - return nil - } - - case reflect.Ptr: - if f.Type == statementType { - // The only case of this should be the - // "Statement" field - if name != "Statement" { - panic(fmt.Sprintf("string field is %s, not Statement", name)) - } - fn = func(stmt *Statement, v, _ reflect.Value, types *typeDictionary) error { - if v.Type() != at { - panic(fmt.Sprintf("got type %v, want %v", v.Type(), at)) - } - v.Elem().Field(i).Set(reflect.ValueOf(stmt)) - return nil - } - break - } - - // Make sure our field type is also setup. - descend(name, f.Type) - - fn = func(stmt *Statement, v, p reflect.Value, types *typeDictionary) error { - if v.Type() != at { - panic(fmt.Sprintf("given type %s, need type %s", v.Type(), at)) - } - fv := v.Elem().Field(i) - if !fv.IsNil() { - return errors.New(stmt.Keyword + ": already set") - } - - // Use build to build the value for this field. - sv, err := build(stmt, v, types) - if err != nil { - return err - } - v.Elem().Field(i).Set(sv) - return nil - } - - case reflect.Slice: - // A slice at this point is always a slice of - // substructures. We may see the same keyword multiple - // times, each time we see it we just append to the - // slice. - st := f.Type.Elem() - switch st.Kind() { - default: - panic(fmt.Sprintf("invalid type: %v", st.Kind())) - case reflect.Ptr: - descend(name, st) - fn = func(stmt *Statement, v, p reflect.Value, types *typeDictionary) error { - if v.Type() != at { - panic(fmt.Sprintf("given type %s, need type %s", v.Type(), at)) - } - sv, err := build(stmt, v, types) - if err != nil { - return err - } - - fv := v.Elem().Field(i) - fv.Set(reflect.Append(fv, sv)) - return nil - } - } - } - y.funcs[name] = fn - } -} diff --git a/src/webui/internal/goyang/pkg/yang/ast_test.go b/src/webui/internal/goyang/pkg/yang/ast_test.go deleted file mode 100644 index 9010e5be0..000000000 --- a/src/webui/internal/goyang/pkg/yang/ast_test.go +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "bytes" - "fmt" - "reflect" - "testing" -) - -type MainNode struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Field *Value `yang:"field"` - Slice []*Value `yang:"slice"` - ChildNode *SubNode `yang:"child_node"` - ChildSlice []*SubNode `yang:"child_slice"` - ReqNode *ReqNode `yang:"req_node"` - MainField *Value `yang:"main_field,required=main_node"` - AltField *Value `yang:"alt_field,required=alt_node"` -} - -func (m *MainNode) Kind() string { - if m.AltField != nil { - return "alt_node" - } - return "main_node" -} - -func (m *MainNode) ParentNode() Node { return m.Parent } -func (m *MainNode) NName() string { return m.Name } -func (m *MainNode) Statement() *Statement { return m.Source } -func (m *MainNode) Exts() []*Statement { return m.Extensions } - -func (m *MainNode) checkEqual(n Node) string { - o, ok := n.(*MainNode) - if !ok { - return fmt.Sprintf("expected *MainNode, got %T", n) - } - if m.Name != o.Name { - return fmt.Sprintf("got name %s, want %s", o.Name, m.Name) - } - if s := m.Source.checkEqual(o.Source); s != "" { - return s - } - if (m.Field == nil) != (o.Field == nil) { - if m.Field == nil { - return "unexpected field entry" - } - return "missing expected field entry" - } - if m.Field != nil { - if m.Field.Name != o.Field.Name { - return fmt.Sprintf("got field of %s, want %s", o.Field.Name, m.Field.Name) - } - } - if len(m.Slice) != len(o.Slice) { - return fmt.Sprintf("got slice of %d, want slice of %d", len(o.Slice), len(m.Slice)) - } - for x, s1 := range m.Slice { - s2 := o.Slice[x] - if s1.Name != s2.Name { - return fmt.Sprintf("slice[%d] got %s, want %s", x, s2.Name, s1.Name) - } - } - if (m.ChildNode == nil) != (o.ChildNode == nil) { - if m.ChildNode == nil { - return "unexpected child_node entry" - } - return "missing expected child_node entry" - } - if m.ChildNode != nil { - if s := m.ChildNode.checkEqual(o.ChildNode); s != "" { - return fmt.Sprintf("child_node: %s", s) - } - } - if len(m.ChildSlice) != len(o.ChildSlice) { - return fmt.Sprintf("got child_slice of %d, want slice of %d", len(o.ChildSlice), len(m.ChildSlice)) - } - for x, s1 := range m.ChildSlice { - s2 := o.ChildSlice[x] - if s := s1.checkEqual(s2); s != "" { - return fmt.Sprintf("child_slice[%d]: %s", x, s) - } - } - if (m.ReqNode == nil) != (o.ReqNode == nil) { - if m.ReqNode == nil { - return "unexpected req_node entry" - } - return "missing expected req_node entry" - } - if m.ReqNode != nil { - if s := m.ReqNode.checkEqual(o.ReqNode); s != "" { - return fmt.Sprintf("req_node: %s", s) - } - } - if (m.AltField == nil) != (o.AltField == nil) { - if m.AltField == nil { - return "unexpected alt_field entry" - } - return "missing expected alt_field entry" - } - if m.AltField != nil { - if m.AltField.Name != o.AltField.Name { - return fmt.Sprintf("got alt_field of %s, want %s", o.AltField.Name, m.AltField.Name) - } - } - return "" -} - -type SubNode struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - SubField *Value `yang:"sub_field"` -} - -func (SubNode) Kind() string { return "sub_node" } -func (s *SubNode) ParentNode() Node { return s.Parent } -func (s *SubNode) NName() string { return s.Name } -func (s *SubNode) Statement() *Statement { return s.Source } -func (s *SubNode) Exts() []*Statement { return s.Extensions } - -func (s *SubNode) checkEqual(o *SubNode) string { - if s.Name != o.Name { - return fmt.Sprintf("got name %s, want %s", o.Name, s.Name) - } - if s := s.Source.checkEqual(o.Source); s != "" { - return s - } - if (s.SubField == nil) != (o.SubField == nil) { - if s.SubField == nil { - return "unexpected sub_field entry" - } - return "missing expected sub_field entry" - } - if s.SubField != nil { - if s.SubField.Name != o.SubField.Name { - return fmt.Sprintf("got sub_field of %s, want %s", o.SubField.Name, s.SubField.Name) - } - } - return "" -} - -type ReqNode struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - - ReqField *Value `yang:"req_field,required"` - AltReqField *Value `yang:"alt_req_field,required=alt_req_node"` - Field *Value `yang:"field"` -} - -func (s *ReqNode) Kind() string { - return "req_node" -} -func (s *ReqNode) ParentNode() Node { return s.Parent } -func (s *ReqNode) NName() string { return s.Name } -func (s *ReqNode) Statement() *Statement { return s.Source } -func (m *ReqNode) Exts() []*Statement { return nil } - -func (s *ReqNode) checkEqual(o *ReqNode) string { - if s.Name != o.Name { - return fmt.Sprintf("got name %s, want %s", o.Name, s.Name) - } - if s := s.Source.checkEqual(o.Source); s != "" { - return s - } - if (s.ReqField == nil) != (o.ReqField == nil) { - if s.ReqField == nil { - return "unexpected req_field entry" - } - return "missing expected req_field entry" - } - if s.ReqField != nil { - if s.ReqField.Name != o.ReqField.Name { - return fmt.Sprintf("got req_field of %s, want %s", o.ReqField.Name, s.ReqField.Name) - } - } - if (s.AltReqField == nil) != (o.AltReqField == nil) { - if s.AltReqField == nil { - return "unexpected alt_req_field entry" - } - return "missing expected alt_req_field entry" - } - if s.AltReqField != nil { - if s.AltReqField.Name != o.AltReqField.Name { - return fmt.Sprintf("got alt_req_field of %s, want %s", o.AltReqField.Name, s.AltReqField.Name) - } - } - return "" -} - -func (s *Statement) checkEqual(o *Statement) string { - if (s == nil) != (o == nil) { - var b bytes.Buffer - if s == nil { - o.Write(&b, "") - return fmt.Sprintf("unexpected Statement entry\n%s", &b) - } - s.Write(&b, "") - return fmt.Sprintf("missing expected Statement entry\n%s", &b) - } - if s == nil { - return "" - } - var b1, b2 bytes.Buffer - s.Write(&b1, "") - o.Write(&b2, "") - ss := b1.String() - os := b2.String() - if ss != os { - return fmt.Sprintf("got statement:\n%swant:\n%s", os, ss) - } - return "" -} - -func TestAST(t *testing.T) { - // Teach the AST parser about our testing nodes - type meta struct { - MainNode []*MainNode `yang:"main_node"` - } - - old_aliases := aliases - aliases = map[string]string{ - "alt_node": "main_node", - } - - for _, tt := range []struct { - line int - in string - out *MainNode - err string - }{ - { - line: line(), - in: ` -main_node the_node { - // This test is testing to make sure unknown statements, that - // might be extensions, are properly put in the Extensions slice. - // When an extension is used, it must be of the form "prefix:name". - // See https://tools.ietf.org/html/rfc6020#section-7.17 - ex:ext1 value1; - ex:ext2 value2; - main_field foo; -} -`, - out: &MainNode{ - Source: SA("main_node", "the_node", - SA("ex:ext1", "value1"), - SA("ex:ext2", "value2"), - SA("main_field", "foo")), - Name: "the_node", - Extensions: []*Statement{ - SA("ex:ext1", "value1"), - SA("ex:ext2", "value2"), - }, - MainField: &Value{ - Name: "foo", - }, - }, - }, - { - line: line(), - in: ` -main_node the_node { - // This test tests fields, slices, and sub-statements. - field field_value; - slice sl1; - slice sl2; - child_node the_child { - sub_field val1; - } - child_slice element1 { - sub_field el1; - } - child_slice element2 { - sub_field el2; - } - main_field foo; -}`, - out: &MainNode{ - Source: SA("main_node", "the_node", - SA("field", "field_value"), - SA("slice", "sl1"), - SA("slice", "sl2"), - SA("child_node", "the_child", - SA("sub_field", "val1")), - SA("child_slice", "element1", - SA("sub_field", "el1")), - SA("child_slice", "element2", - SA("sub_field", "el2")), - SA("main_field", "foo"), - ), - Name: "the_node", - Field: &Value{ - Name: "field_value", - }, - Slice: []*Value{ - { - Name: "sl1", - }, - { - Name: "sl2", - }, - }, - ChildNode: &SubNode{ - Source: SA("child_node", "the_child", - SA("sub_field", "val1")), - Name: "the_child", - SubField: &Value{ - Name: "val1", - }, - }, - ChildSlice: []*SubNode{ - { - Source: SA("child_slice", "element1", - SA("sub_field", "el1")), - Name: "element1", - SubField: &Value{ - Name: "el1", - }, - }, - { - Source: SA("child_slice", "element2", - SA("sub_field", "el2")), - Name: "element2", - SubField: &Value{ - Name: "el2", - }, - }, - }, - MainField: &Value{ - Name: "foo", - }, - }, - }, - { - line: line(), - in: ` -// This test tests for the presence of a required field. -// main_node requires the field named "main_field". -main_node the_node { - main_field value1 { - } -} -`, - out: &MainNode{ - Source: SA("main_node", "the_node", - SA("main_field", "value1"), - ), - Name: "the_node", - MainField: &Value{ - Name: "value1", - }, - }, - }, - { - line: line(), - in: ` -// This test tests for the presence of a required= field. -// alt_node requires the field named "alt_field". -alt_node the_node { - alt_field value2 { - } -} -`, - out: &MainNode{ - Source: SA("alt_node", "the_node", - SA("alt_field", "value2"), - ), - Name: "the_node", - AltField: &Value{ - Name: "value2", - }, - }, - }, - { - line: line(), - in: ` -main_node the_node { - // This test tests that extensions are rejected when the node is not - // supposed to contain them. - req_node value1 { - req_field foo { - } - ex:ext1 value1; - ex:ext2 value2; - } -} -`, - err: `ast.yang:8:3: no extension function`, - }, - { - line: line(), - in: ` -main_node the_node { - // This test tests for the presence of a required field. - // req_node requires the field named "req_field". - req_node value1 { - req_field foo { - } - } - main_field foo; -} -`, - out: &MainNode{ - Source: SA("main_node", "the_node", - SA("req_node", "value1", - SA("req_field", "foo")), - SA("main_field", "foo"), - ), - Name: "the_node", - ReqNode: &ReqNode{ - Source: SA("req_node", "value1", - SA("req_field", "foo")), - Name: "value1", - ReqField: &Value{ - Name: "foo", - }, - }, - MainField: &Value{ - Name: "foo", - }, - }, - }, - { - line: line(), - in: ` -main_node the_node { - // This test tests that the absence of a required field fails. - // req_node requires the field named "req_field". - req_node value1 { - } - main_field foo; -} -`, - err: `ast.yang:5:2: missing required req_node field: req_field`, - }, - { - line: line(), - in: ` -main_node the_node { - // This test tests that the absence of a required field. - // main_node requires the field named "main_field". - req_node value1 { - req_field foo { - } - } -} -`, - err: `ast.yang:2:1: missing required main_node field: main_field`, - }, - { - line: line(), - in: ` -// This test tests that the alt_field, specified with -// required=alt_node, causes the AST construction to error when a -// main_node contains it. -main_node the_node { - main_field foo; - alt_field foo; -} -`, - err: `ast.yang:5:1: unknown main_node field: alt_field`, - }, - { - line: line(), - in: ` -// This test tests that required=alt_node enforces that -// alt_node must contain it. -alt_node the_node { - main_field foo; - alt_field foo; -} -`, - err: `ast.yang:4:1: unknown alt_node field: main_field`, - }, - { - line: line(), - in: ` -// This test tests that required=alt_node enforces that -// alt_node must contain it. -alt_node the_node { -} -`, - err: `ast.yang:4:1: missing required alt_node field: alt_field`, - }, - } { - ss, err := Parse(tt.in, "ast.yang") - if err != nil { - t.Errorf("%d: %v", tt.line, err) - continue - } - if len(ss) != 1 { - t.Errorf("%d: got %d results, want 1", tt.line, len(ss)) - continue - } - - typeDict := newTypeDictionary() - initTypes(reflect.TypeOf(&meta{})) - - ast, err := buildASTWithTypeDict(ss[0], typeDict) - switch { - case err == nil && tt.err == "": - if s := tt.out.checkEqual(ast); s != "" { - t.Errorf("%d: %s", tt.line, s) - } - case err == nil: - t.Errorf("%d: did not get expected error %s", tt.line, tt.err) - case tt.err == "": - t.Errorf("%d: %v", tt.line, err) - case err.Error() != tt.err: - t.Errorf("%d: got error %v, want %s", tt.line, err, tt.err) - } - } - - aliases = old_aliases -} diff --git a/src/webui/internal/goyang/pkg/yang/bgp_test.go b/src/webui/internal/goyang/pkg/yang/bgp_test.go deleted file mode 100644 index 1d96a6d5c..000000000 --- a/src/webui/internal/goyang/pkg/yang/bgp_test.go +++ /dev/null @@ -1,571 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "reflect" - "testing" -) - -// TestBGP simply makes sure we are able to parse a version of Anees's -// BGP model. We don't actually attempt to validate we got the right -// AST. ast_test.go will test smaller peices to make sure the basics -// of BuildAST produce expected results. -func TestBGP(t *testing.T) { - ss, err := Parse(bgp, "bgp.yang") - if err != nil { - t.Fatal(err) - } - if len(ss) != 1 { - t.Fatalf("got %d results, want 1", len(ss)) - } - typeDict := newTypeDictionary() - initTypes(reflect.TypeOf(&meta{})) - if _, err := buildASTWithTypeDict(ss[0], typeDict); err != nil { - t.Fatal(err) - } -} - -var bgp = ` -module google-bgp { - - yang-version "1"; - - // namespace - namespace "http://google.com/yang/google-bgp-protocol-cfg"; - - prefix "gbgp"; - - // import some basic types -- no other dependency on - // in-progress models in draft status - import ietf-inet-types { prefix inet; } - - - // meta - organization "Google, Inc."; - - contact - "Google, Inc. - 1600 Amphitheatre Way - Mountain View, CA 94043"; - - description - "This module describes a YANG model for BGP protocol - configuration.It is a limited subset of all of the configuration - parameters available in the variety of vendor implementations, - hence it is expected that it would be augmented with vendor- - specific configuration data as needed.Additional modules or - submodules to handle other aspects of BGP configuration, - including policy, VRFs, and additional address families are also - expected."; - - revision "2014-07-07" { - description - "Initial revision"; - reference "TBD"; - } - - - identity afi-type { - description - "base identity type for BGP address family identifiers (AFI)"; - reference "IETF RFC 4760"; - } - - identity safi-type { - description - "base identity type for BGP subsequent address family - identifiers (SAFI)"; - reference "IETF RFC 4760"; - } - - identity ipv4-afi { - base afi-type; - description - "IPv4 AF identifier"; - } - - identity ipv6-afi { - base afi-type; - description - "IPv6 AF identifier"; - } - - identity unicast-safi { - base safi-type; - description - "unicast SAFI identifier"; - } - - identity labeled-unicast-safi { - base safi-type; - description - "labeled unicast SAFI identifier"; - reference "RFC 3107 - Carrying Label Information in BGP-4"; - } - - - typedef peer-group-type { - type enumeration { - enum INTERNAL { - description "internal (iBGP) peer"; - } - enum EXTERNAL { - description "external (eBGP) peer"; - } - } - description - "labels a peer as explicitly internal or external"; - } - - - typedef remove-private-as-option { - type enumeration { - enum ALL { - description "remove all private ASes in the path"; - } - enum REPLACE { - description "replace private ASes with local AS"; - } - } - description - "set of options for configuring how private AS path numbers - are removed from advertisements"; - } - - typedef percentage { - type uint8 { - range "0..100"; - } - description - "Integer indicating a percentage value"; - } - - typedef rr-cluster-id-type { - type union { - type uint32; - type inet:ipv4-address; - } - description - "union type for route reflector cluster ids: - option 1: 4-byte number - option 2: IP address"; - } - - grouping bgp-common-configuration { - description "Common configuration across neighbors, groups, - etc."; - - leaf description { - type string; - description - "A textual description of the peer or group"; - } - container use-multiple-paths { - description - "Configuration of BGP multipath to enable load sharing across - multiple paths to peers."; - leaf allow-multiple-as { - type boolean; - default "false"; - description - "Allow multipath to use paths from different neighboring - ASes. The default is to only consider multiple paths from - the same neighboring AS."; - } - leaf maximum-paths { - type uint32; - default 1; - description - "Maximum number of parallel paths to consider when using - BGP multipath. The default is to use a single path."; - reference "draft-ietf-idr-add-paths-09.txt"; - } - } - - } - - grouping bgp-group-common-configuration { - description "Configuration items that are applied at the peer - group level"; - } - - grouping bgp-group-neighbor-common-configuration { - description "Configuration options for peer and group context"; - - leaf auth-password { - type string; - description - "Configures an authentication password for use with - neighboring devices."; - } - - container timers { - description "Configuration of various BGP timers"; - - leaf hold-time { - type decimal64 { - fraction-digits 2; - } - default 90; - // hold-time should typically be set to 3x the - // keepalive-interval -- create a constraint for this? - description - "Time interval in seconds that a BGP session will be - considered active in the absence of keepalive or other - messages from the peer"; - reference - "RFC 1771 - A Border Gateway Protocol 4"; - } - - leaf keepalive-interval { - type decimal64 { - fraction-digits 2; - } - default 30; - description - "Time interval in seconds between transmission of keepalive - messages to the neighbor. Typically set to 1/3 the - hold-time."; - } - - leaf advertisement-interval { - type decimal64 { - fraction-digits 2; - } - default 30; - description - "Mininum time interval in seconds between transmission - of BGP updates to neighbors"; - reference - "RFC 1771 - A Border Gateway Protocol 4"; - } - - leaf connect-retry { - type decimal64 { - fraction-digits 2; - } - default 30; - description - "Time interval in seconds between attempts to establish a - session with the peer."; - } - } - - container ebgp-multihop { - description - "Configure multihop BGP for peers that are not directly - connected"; - - leaf multihop-ttl { - type uint8; - default 1; - description - "Time-to-live for multihop BGP sessions. The default - value of 1 is for directly connected peers (i.e., - multihop disabled"; - - } - - } - - container route-reflector { - description - "Configure the local router as a route-reflector - server"; - leaf route-reflector-clusterid { - type rr-cluster-id-type; - description - "route-reflector cluster id to use when local router is - configured as a route reflector. Commonly set at the group - level, but allows a different cluster - id to be set for each neighbor."; - } - - leaf route-reflector-client { - type boolean; - default "false"; - description - "configure the neighbor as a route reflector client"; - } - } - - leaf remove-private-as { - // could also make this a container with a flag to enable - // remove-private and separate option. here, option implies - // remove-private is enabled. - type remove-private-as-option; - description - "Remove private AS numbers from updates sent to peers"; - } - - - container bgp-logging-options { - description - "Configure various tracing/logging options for BGP peers - or groups. Expected that additional vendor-specific log - options would augment this container"; - - leaf log-neighbor-state-changes { - type boolean; - default "true"; - description - "Configure logging of peer state changes. Default is - to enable logging of peer state changes."; - } - } - - container transport-options { - description - "Transport protocol options for BGP sessions"; - - leaf tcp-mss { - type uint16; - description - "Sets the max segment size for BGP TCP sessions"; - } - - leaf passive-mode { - type boolean; - description - "Wait for peers to issue requests to open a BGP session, - rather than initiating sessions from the local router"; - } - } - - leaf local-address { - type inet:ip-address; - description - "Set the local IP (either IPv4 or IPv6) address to use for - the session when sending BGP update messages"; - } - - leaf route-flap-damping { - type boolean; - description - "Enable route flap damping"; - } - } - - grouping bgp-address-family-common-configuration { - description "Configuration options per address family context"; - - list address-family { - - key "afi-name"; - description - "Per address-family configuration, uniquely identified by AF - name"; - leaf afi-name { - type identityref { - base "afi-type"; - } - description - "Address family names are drawn from the afi-type base - identity, which has specific address family types as - derived identities"; - } - - list subsequent-address-family { - - key "safi-name"; - description - "Per subsequent address family configuration, under a - specific address family"; - - leaf safi-name { - // do we need to specify which SAFIs are possible within - // each AF? with the current set of AF/SAFI, all are - /// applicable - type identityref { - base "safi-type"; - } - description - "Within each address family, subsequent address family - names are drawn from the subsequent-address-family base - identity"; - } - - - container prefix-limit { - description - "Configure the maximum number of prefixes that will be - accepted from a peer"; - - leaf max-prefixes { - type uint32; - description - "Maximum number of prefixes that will be accepted from - the neighbor"; - } - - leaf shutdown-threshold-pct { - type percentage; - description - "Threshold on number of prefixes that can be received - from a neighbor before generation of warning messages - or log entries. Expressed as a percentage of - max-prefixes."; - } - - leaf restart-timer { - type decimal64 { - fraction-digits 2; - } - units "seconds"; - description - "Time interval in seconds after which the BGP session - is reestablished after being torn down due to exceeding - the max-prefixes limit."; - } - } - } - } - } - - - - container bgp { - description "Top-level configuration data for the BGP router"; - - container global { - description - "Top-level bgp protocol options applied across peer-groups, - neighbors, and address families"; - - leaf as { - type inet:as-number; - mandatory "true"; - description - "Local autonomous system number of the router. Uses - the as-number type defined in RFC 6991"; - } - leaf router-id { - type inet:ipv4-address; - description - "Router id of the router, expressed as an - IPv4 address"; - // there is a typedef for this in draft module ietf-routing - // but it does not use an appropriate type - } - container route-selection-options { - description - "Set of configuration options that govern best - path selection"; - leaf always-compare-med { - type boolean; - default "false"; - description - "Compare multi-exit discriminator (MED) value from - different ASes when selecting the best route. The - default behavior is to only compare MEDs for paths - received from the same AS."; - } - leaf ignore-as-path { - type boolean; - default "false"; - description - "Ignore the AS path length when selecting the best path. - The default is to use the AS path length and prefer paths - with shorter length."; - } - leaf external-compare-router-id { - type boolean; - default "true"; - description - "When comparing similar routes received from external - BGP peers, use the router-id as a criterion to select - the active path. The default is to use the router-id to - select among similar routes."; - } - leaf advertise-inactive-routes { - type boolean; - default "false"; - description - "Advertise inactive routes to external peers. The - default is to only advertise active routes."; - } - } - container default-route-distance { - description - "Administrative distance (or preference) assigned to - routes received from different sources - (external, internal, and local.)"; - leaf external-route-distance { - type uint8 { - range "1..255"; - } - description - "Administrative distance for routes learned from external - BGP (eBGP)"; - } - leaf internal-route-distance { - type uint8 { - range "1..255"; - } - description - "Administrative distance for routes learned from internal - BGP (iBGP)"; - - } - } - } - - uses bgp-address-family-common-configuration; - - list peer-group { - key "group-name"; - description - "List of peer-groups, uniquely identified by the peer group - names"; - leaf group-name { - type string; - description "Name of the peer group"; - } - leaf group-type { - type peer-group-type; - description - "Explicitly designate the peer group as internal (iBGP) - or external (eBGP)"; - } - uses bgp-common-configuration; - uses bgp-address-family-common-configuration; - uses bgp-group-neighbor-common-configuration; - } - - list neighbor { - key "neighbor-address"; - description - "List of BGP peers, uniquely identified by neighbor address"; - leaf neighbor-address { - type inet:ip-address; - description - "Address of the BGP peer, either IPv4 or IPv6"; - } - - leaf peer-as { - type inet:as-number; - mandatory "true"; - description - "AS number of the peer"; - - } - uses bgp-common-configuration; - uses bgp-address-family-common-configuration; - uses bgp-group-neighbor-common-configuration; - } - - } -}` diff --git a/src/webui/internal/goyang/pkg/yang/camelcase.go b/src/webui/internal/goyang/pkg/yang/camelcase.go deleted file mode 100644 index 2704ddcd5..000000000 --- a/src/webui/internal/goyang/pkg/yang/camelcase.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -var knownWords = map[string]string{ - "Ietf": "IETF", -} - -// Is c an ASCII lower-case letter? -func isASCIILower(c byte) bool { - return 'a' <= c && c <= 'z' -} - -// Is c an ASCII digit? -func isASCIIDigit(c byte) bool { - return '0' <= c && c <= '9' -} - -// CamelCase returns a CamelCased name for a YANG identifier. -// Currently this supports the output being used for a Go or proto identifier. -// Dash and dot are first converted to underscore, and then any underscores -// before a lower-case letter are removed, and the letter converted to -// upper-case. Any input characters not part of the YANG identifier -// specification (https://tools.ietf.org/html/rfc7950#section-6.2) are treated -// as lower-case characters. -// The first letter is always upper-case in order to be an exported name in Go. -// There is a remote possibility of this rewrite causing a name collision, but -// it's so remote we're prepared to pretend it's nonexistent - since the C++ -// generator lowercases names, it's extremely unlikely to have two fields with -// different capitalizations. In short, _my_field-name_2 becomes XMyFieldName_2. -func CamelCase(s string) string { - if s == "" { - return "" - } - - fix := func(c byte) byte { - if c == '-' || c == '.' { - return '_' - } - return c - } - - t := make([]byte, 0, 32) - i := 0 - if fix(s[0]) == '_' { - // Need a capital letter; drop the '_'. - t = append(t, 'X') - i++ - } - - // Invariant: if the next letter is lower case, it must be converted - // to upper case. - // That is, we process a word at a time, where words are marked by _ or - // upper case letter. Digits are treated as words. - for ; i < len(s); i++ { - c := fix(s[i]) - if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { - continue // Skip the underscore in s. - } - if isASCIIDigit(c) { - t = append(t, c) - continue - } - // Assume we have a letter now - if not, it's a bogus identifier. - // The next word is a sequence of characters that must start upper case. - if isASCIILower(c) { - c ^= ' ' // Make it a capital letter. - } - start := len(t) - t = append(t, c) // Guaranteed not lower case. - // Accept lower case sequence that follows. - for i+1 < len(s) && isASCIILower(s[i+1]) { - i++ - t = append(t, s[i]) - } - // If the word turns out to be a special word, then use that instead. - if kn := knownWords[string(t[start:])]; kn != "" { - t = append(t[:start], []byte(kn)...) - } - } - return string(t) -} diff --git a/src/webui/internal/goyang/pkg/yang/camelcase_test.go b/src/webui/internal/goyang/pkg/yang/camelcase_test.go deleted file mode 100644 index 0539bfa8e..000000000 --- a/src/webui/internal/goyang/pkg/yang/camelcase_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "testing" -) - -func TestCamelCase(t *testing.T) { - tests := []struct { - in, want string - }{ - {"one", "One"}, - {"one_two", "OneTwo"}, - {"__one__two__three__four", "XOne_Two_Three_Four"}, - {"one.two.three", "OneTwoThree"}, - {"one.two.three.", "OneTwoThree_"}, - {"_my_field_name_2", "XMyFieldName_2"}, - {"Something_Capped", "Something_Capped"}, - {"_Foo-bar", "XFooBar"}, - {"my_Name", "My_Name"}, - {"OneTwo", "OneTwo"}, - {"_", "X"}, - {"_a_", "XA_"}, - {"ietf-interface", "IETFInterface"}, - {"ietf-interface-1", "IETFInterface_1"}, - {"out-unicast.pkts", "OutUnicastPkts"}, - // Invalid input conversion behaviours: - {"one/two", "One/two"}, - {"/one/two", "/one/two"}, - {"one:two", "One:two"}, - {"::one::two", "::one::two"}, - {"one|two", "One|two"}, - {"one||two", "One||two"}, - } - for _, tc := range tests { - if got := CamelCase(tc.in); got != tc.want { - t.Errorf("CamelCase(%q) = %q, want %q", tc.in, got, tc.want) - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/doc.go b/src/webui/internal/goyang/pkg/yang/doc.go deleted file mode 100644 index 3dab829c4..000000000 --- a/src/webui/internal/goyang/pkg/yang/doc.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package yang is used to parse .yang files (see RFC 6020). -// -// A generic yang statements takes one of the forms: -// -// keyword [argument] ; -// keyword [argument] { [statement [...]] } -// -// At the lowest level, package yang returns a simple tree of statements via the -// Parse function. The Parse function makes no attempt to determine the -// validity of the source, other than checking for generic syntax errors. -// -// At it's simplest, the GetModule function is used. The GetModule function -// searches the current directory, and any directory added to the Path variable, -// for a matching .yang source file by appending .yang to the name of the -// module: -// -// // Get the tree for the module module-name by looking for the source -// // file named module-name.yang. -// e, errs := yang.GetModule("module-name" [, optional sources...]) -// if len(errs) > 0 { -// for _, err := range errs { -// fmt.Fprintln(os.Stderr, err) -// } -// os.Exit(1) -// } -// -// // e is the Entry tree for "module-name" -// -// More complicated uses cases should use NewModules and then some combination -// of Modules.GetModule, Modules.Read, Modules.Parse, and Modules.GetErrors. -// -// The GetErrors method is mandatory, however, both yang.GetModule and -// Modules.GetModule automatically call Modules.GetErrors. -package yang diff --git a/src/webui/internal/goyang/pkg/yang/entry.go b/src/webui/internal/goyang/pkg/yang/entry.go deleted file mode 100644 index cbba30b7d..000000000 --- a/src/webui/internal/goyang/pkg/yang/entry.go +++ /dev/null @@ -1,1664 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// The file contains the code to convert an AST (Node) tree into an Entry tree -// via the ToEntry function. The entry tree, once fully resolved, is the -// product of this package. The tree should have all types and references -// resolved. -// -// TODO(borman): handle types, leafrefs, and extensions - -import ( - "errors" - "fmt" - "io" - "math" - "reflect" - "sort" - "strconv" - "strings" - - "github.com/openconfig/goyang/pkg/indent" -) - -// A TriState may be true, false, or unset -type TriState int - -// The possible states of a TriState. -const ( - TSUnset = TriState(iota) - TSTrue - TSFalse -) - -// Value returns the value of t as a boolean. Unset is returned as false. -func (t TriState) Value() bool { - return t == TSTrue -} - -// String displays t as a string. -func (t TriState) String() string { - switch t { - case TSUnset: - return "unset" - case TSTrue: - return "true" - case TSFalse: - return "false" - default: - return fmt.Sprintf("ts-%d", t) - } -} - -// deviationPresence stores whether certain attributes for a DeviateEntry-type -// Entry have been given deviation values. This is useful when the attribute -// doesn't have a presence indicator (e.g. non-pointers). -type deviationPresence struct { - hasMinElements bool - hasMaxElements bool -} - -// Entry represents a single schema tree node, which can be a directory -// (containing a subtree) or a leaf node (which contains YANG types that have -// no children, e.g., leaf, leaf-list). They can be distinguished by whether -// their "Dir" field is nil. This object is created from a corresponding AST -// node after applying modifications (i.e. uses, augments, deviations). If -// Errors is not nil then it means semantic errors existed while converting the -// AST, in which case the only other valid field other than Errors is Node. -type Entry struct { - Parent *Entry `json:"-"` - Node Node `json:"-"` // the base node this Entry was derived from. - Name string // our name, same as the key in our parent Dirs - Description string `json:",omitempty"` // description from node, if any - // Default value for the node, if any. Note that only leaf-lists may - // have more than one value. For all other types, use the - // SingleDefaultValue() method to access the default value. - Default []string `json:",omitempty"` - Units string `json:",omitempty"` // units associated with the type, if any - Errors []error `json:"-"` // list of errors encountered on this node - Kind EntryKind // kind of Entry - Config TriState // config state of this entry, if known - Prefix *Value `json:",omitempty"` // prefix to use from this point down - Mandatory TriState `json:",omitempty"` // whether this entry is mandatory in the tree - - // Fields associated with directory nodes - Dir map[string]*Entry `json:",omitempty"` - Key string `json:",omitempty"` // Optional key name for lists (i.e., maps) - - // Fields associated with leaf nodes - Type *YangType `json:",omitempty"` - - // Extensions found - Exts []*Statement `json:",omitempty"` - - // Fields associated with list nodes (both lists and leaf-lists) - ListAttr *ListAttr `json:",omitempty"` - - RPC *RPCEntry `json:",omitempty"` // set if we are an RPC - - // Identities that are defined in this context, this is set if the Entry - // is a module only. - Identities []*Identity `json:",omitempty"` - - Augments []*Entry `json:",omitempty"` // Augments defined in this entry. - Augmented []*Entry `json:",omitempty"` // Augments merged into this entry. - Deviations []*DeviatedEntry `json:"-"` // Deviations associated with this entry. - Deviate map[deviationType][]*Entry `json:"-"` - // deviationPresence tracks whether certain attributes for a DeviateEntry-type - // Entry have been given deviation values. - deviatePresence deviationPresence - Uses []*UsesStmt `json:",omitempty"` // Uses merged into this entry. - - // Extra maps all the unsupported fields to their values - Extra map[string][]interface{} `json:"extra-unstable,omitempty"` - - // Annotation stores annotated values, and is not populated by this - // library but rather can be used by calling code where additional - // information should be stored alongside the Entry. - Annotation map[string]interface{} `json:",omitempty"` - - // namespace stores the namespace of the Entry if it overrides the - // root namespace within the schema tree. This is the case where an - // entry is augmented into the tree, and it retains the namespace of - // the augmenting entity per RFC6020 Section 7.15.2. The namespace - // of the Entry should be accessed using the Namespace function. - namespace *Value -} - -// An RPCEntry contains information related to an RPC Node. -type RPCEntry struct { - Input *Entry - Output *Entry -} - -// A ListAttr is associated with an Entry that represents a List node -type ListAttr struct { - MinElements uint64 // leaf-list or list MUST have at least min-elements - MaxElements uint64 // leaf-list or list has at most max-elements - // OrderedBy is deprecated. Use OrderedByUser instead. - OrderedBy *Value - // OrderedByUser indicates whether the entries are "ordered-by user". - // Otherwise the order is determined by the system. - OrderedByUser bool -} - -// parseOrderedBy parses the ordered-by value and classifies the list/leaf-list -// by whether the `ordered-by user` modifier is active. -// -// For more information see -// https://datatracker.ietf.org/doc/html/rfc7950#section-7.7.7 -func (l *ListAttr) parseOrderedBy(s *Value) error { - if s == nil { - return nil - } - l.OrderedBy = s - switch s.Name { - case "user": - l.OrderedByUser = true - case "system": - default: - return fmt.Errorf("%s: ordered-by has invalid argument: %q", Source(s), s.Name) - } - return nil -} - -// NewDefaultListAttr returns a new ListAttr object with min/max elements being -// set to 0/math.MaxUint64 respectively. -func NewDefaultListAttr() *ListAttr { - return &ListAttr{ - MinElements: 0, - MaxElements: math.MaxUint64, - } -} - -// A UsesStmt associates a *Uses with its referenced grouping *Entry -type UsesStmt struct { - Uses *Uses - Grouping *Entry -} - -// Modules returns the Modules structure that e is part of. This is needed -// when looking for rooted nodes not part of this Entry tree. -func (e *Entry) Modules() *Modules { - for e.Parent != nil { - e = e.Parent - } - return e.Node.(*Module).Modules -} - -// IsDir returns true if e is a directory. -func (e *Entry) IsDir() bool { - return e.Dir != nil -} - -// IsLeaf returns true if e is a leaf i.e. is not a container, list, leaf-list, -// choice or case statement. -func (e *Entry) IsLeaf() bool { - return !e.IsDir() && e.Kind == LeafEntry && e.ListAttr == nil -} - -// IsLeafList returns true if e is a leaf-list. -func (e *Entry) IsLeafList() bool { - return !e.IsDir() && e.Kind == LeafEntry && e.ListAttr != nil -} - -// IsList returns true if e is a list. -func (e *Entry) IsList() bool { - return e.IsDir() && e.ListAttr != nil -} - -// IsContainer returns true if e is a container. -func (e *Entry) IsContainer() bool { - return e.Kind == DirectoryEntry && e.ListAttr == nil -} - -// IsChoice returns true if the entry is a choice node within the schema. -func (e *Entry) IsChoice() bool { - return e.Kind == ChoiceEntry -} - -// IsCase returns true if the entry is a case node within the schema. -func (e *Entry) IsCase() bool { - return e.Kind == CaseEntry -} - -// Print prints e to w in human readable form. -func (e *Entry) Print(w io.Writer) { - if e.Description != "" { - fmt.Fprintln(w) - fmt.Fprintln(indent.NewWriter(w, "// "), e.Description) - } - if e.ReadOnly() { - fmt.Fprintf(w, "RO: ") - } else { - fmt.Fprintf(w, "rw: ") - } - if e.Type != nil { - fmt.Fprintf(w, "%s ", e.Type.Name) - } - switch { - case e.Dir == nil && e.ListAttr != nil: - fmt.Fprintf(w, "[]%s\n", e.Name) - return - case e.Dir == nil: - fmt.Fprintf(w, "%s\n", e.Name) - return - case e.ListAttr != nil: - fmt.Fprintf(w, "[%s]%s {\n", e.Key, e.Name) //} - default: - fmt.Fprintf(w, "%s {\n", e.Name) //} - } - var names []string - for k := range e.Dir { - names = append(names, k) - } - sort.Strings(names) - for _, k := range names { - e.Dir[k].Print(indent.NewWriter(w, " ")) - } - // { to match the brace below to keep brace matching working - fmt.Fprintln(w, "}") -} - -// An EntryKind is the kind of node an Entry is. All leaf nodes are of kind -// LeafEntry. A LeafList is also considered a leaf node. All other kinds are -// directory nodes. -type EntryKind int - -// Enumeration of the types of entries. -const ( - LeafEntry = EntryKind(iota) - DirectoryEntry - AnyDataEntry - AnyXMLEntry - CaseEntry - ChoiceEntry - InputEntry - NotificationEntry - OutputEntry - DeviateEntry -) - -// EntryKindToName maps EntryKind to their names -var EntryKindToName = map[EntryKind]string{ - LeafEntry: "Leaf", - DirectoryEntry: "Directory", - AnyDataEntry: "AnyData", - AnyXMLEntry: "AnyXML", - CaseEntry: "Case", - ChoiceEntry: "Choice", - InputEntry: "Input", - NotificationEntry: "Notification", - OutputEntry: "Output", - DeviateEntry: "Deviate", -} - -func (k EntryKind) String() string { - if s := EntryKindToName[k]; s != "" { - return s - } - return fmt.Sprintf("unknown-entry-%d", k) -} - -// newDirectory returns an empty directory Entry. -func newDirectory(n Node) *Entry { - return &Entry{ - Kind: DirectoryEntry, - Dir: make(map[string]*Entry), - Node: n, - Name: n.NName(), - Extra: map[string][]interface{}{}, - } -} - -// newLeaf returns an empty leaf Entry. -func newLeaf(n Node) *Entry { - return &Entry{ - Kind: LeafEntry, - Node: n, - Name: n.NName(), - Extra: map[string][]interface{}{}, - } -} - -// newError returns an error Entry using format and v to create the error -// contained in the node. The location of the error is prepended. -func newError(n Node, format string, v ...interface{}) *Entry { - e := &Entry{Node: n} - e.errorf("%s: "+format, append([]interface{}{Source(n)}, v...)...) - return e -} - -// errorf appends the error constructed from string and v to the list of errors -// on e. -func (e *Entry) errorf(format string, v ...interface{}) { - e.Errors = append(e.Errors, fmt.Errorf(format, v...)) -} - -// addError appends err to the list of errors on e if err is not nil. -func (e *Entry) addError(err error) { - if err != nil { - e.Errors = append(e.Errors, err) - } -} - -// importErrors imports all the errors from c and its children into e. -func (e *Entry) importErrors(c *Entry) { - if c == nil { - return - } - for _, err := range c.Errors { - e.addError(err) - } - // TODO(borman): need to determine if the extensions have errors - // for _, ce := range e.Exts { - // e.importErrors(ce) - // } - for _, ce := range c.Dir { - e.importErrors(ce) - } -} - -// checkErrors calls f on every error found in the tree e and its children. -func (e *Entry) checkErrors(f func(error)) { - if e == nil { - return - } - for _, e := range e.Dir { - e.checkErrors(f) - } - for _, err := range e.Errors { - f(err) - } - // TODO(borman): need to determine if the extensions have errors - // for _, e := range e.Exts { - // e.checkErrors(f) - // } -} - -// GetErrors returns a sorted list of errors found in e. -func (e *Entry) GetErrors() []error { - // the seen map is used to eliminate duplicate errors. - // Some entries will be processed more than once - // (groupings in particular) and as such may cause - // duplication of errors. - seen := map[error]bool{} - var errs []error - e.checkErrors(func(err error) { - if !seen[err] { - errs = append(errs, err) - seen[err] = true - } - }) - return errorSort(errs) -} - -// add adds the directory entry key assigned to the provided value. -func (e *Entry) add(key string, value *Entry) *Entry { - value.Parent = e - if e.Dir[key] != nil { - e.errorf("%s: duplicate key from %s: %s", Source(e.Node), Source(value.Node), key) - return e - } - e.Dir[key] = value - return e -} - -// delete removes the directory entry key from the entry. -func (e *Entry) delete(key string) { - if _, ok := e.Dir[key]; !ok { - e.errorf("%s: unknown child key %s", Source(e.Node), key) - } - delete(e.Dir, key) -} - -// GetWhenXPath returns the when XPath statement of e if able. -func (e *Entry) GetWhenXPath() (string, bool) { - switch n := e.Node.(type) { - case *Container: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *Leaf: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *LeafList: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *List: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *Choice: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *Case: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *AnyXML: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *AnyData: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - case *Augment: - if n.When != nil && n.When.Statement() != nil { - return n.When.Statement().Arg() - } - } - return "", false -} - -// deviationType specifies an enumerated value covering the different substatements -// to the deviate statement. -type deviationType int64 - -const ( - // DeviationUnset specifies that the argument was unset, which is invalid. - DeviationUnset deviationType = iota - // DeviationNotSupported corresponds to the not-supported deviate argument. - DeviationNotSupported - // DeviationAdd corresponds to the add deviate argument to the deviate stmt. - DeviationAdd - // DeviationReplace corresponds to the replace argument to the deviate stmt. - DeviationReplace - // DeviationDelete corresponds to the delete argument to the deviate stmt. - DeviationDelete -) - -var ( - // fromDeviation maps from an enumerated deviation type to the YANG keyword. - fromDeviation = map[deviationType]string{ - DeviationNotSupported: "not-supported", - DeviationAdd: "add", - DeviationReplace: "replace", - DeviationDelete: "delete", - DeviationUnset: "unknown", - } - - // toDeviation maps from the YANG keyword to an enumerated deviation type. - toDeviation = map[string]deviationType{ - "not-supported": DeviationNotSupported, - "add": DeviationAdd, - "replace": DeviationReplace, - "delete": DeviationDelete, - } -) - -func (d deviationType) String() string { - return fromDeviation[d] -} - -// DeviatedEntry stores a wrapped Entry that corresponds to a deviation. -type DeviatedEntry struct { - Type deviationType // Type specifies the deviation type. - DeviatedPath string // DeviatedPath corresponds to the path that is being deviated. - // Entry is the embedded Entry storing the deviations that are made. Fields - // are set to the value in the schema after the deviation has been applied. - *Entry -} - -// semCheckMaxElements checks whether the max-element argument is valid, and returns the specified value. -func semCheckMaxElements(v *Value) (uint64, error) { - if v == nil || v.Name == "unbounded" { - return math.MaxUint64, nil - } - val, err := strconv.ParseUint(v.Name, 10, 64) - if err != nil { - return val, fmt.Errorf(`%s: invalid max-elements value %q (expect "unbounded" or a positive integer): %v`, Source(v), v.Name, err) - } - if val == 0 { - return val, fmt.Errorf(`%s: invalid max-elements value 0 (expect "unbounded" or a positive integer)`, Source(v)) - } - return val, nil -} - -// semCheckMinElements checks whether the min-element argument is valid, and returns the specified value. -func semCheckMinElements(v *Value) (uint64, error) { - if v == nil { - return 0, nil - } - val, err := strconv.ParseUint(v.Name, 10, 64) - if err != nil { - return val, fmt.Errorf(`%s: invalid min-elements value %q (expect a non-negative integer): %v`, Source(v), v.Name, err) - } - return val, nil -} - -// ToEntry expands node n into a directory Entry. Expansion is based on the -// YANG tags in the structure behind n. ToEntry must only be used -// with nodes that are directories, such as top level modules and sub-modules. -// ToEntry never returns nil. Any errors encountered are found in the Errors -// fields of the returned Entry and its children. Use GetErrors to determine -// if there were any errors. -func ToEntry(n Node) (e *Entry) { - if n == nil { - err := errors.New("ToEntry called on nil AST node") - return &Entry{ - Node: &ErrorNode{Error: err}, - Errors: []error{err}, - } - } - ms := RootNode(n).Modules - if e := ms.getEntryCache(n); e != nil { - return e - } - defer func() { - ms.setEntryCache(n, e) - }() - - // Copy in the extensions from our Node, if any. - defer func(n Node) { - if e != nil { - e.Exts = append(e.Exts, n.Exts()...) - } - }(n) - - // tristateValue returns TSTrue if i contains the value of true, TSFalse - // if it contains the value of false, and TSUnset if i does not have - // a set value (for instance, i is nil). An error is returned if i - // contains a value other than true or false. - tristateValue := func(i interface{}) (TriState, error) { - if v, ok := i.(*Value); ok && v != nil { - switch v.Name { - case "true": - return TSTrue, nil - case "false": - return TSFalse, nil - default: - return TSUnset, fmt.Errorf("%s: invalid config value: %s", Source(n), v.Name) - } - } - return TSUnset, nil - } - - var err error - // Handle non-directory nodes (leaf, leafref, and oddly enough, uses). - switch s := n.(type) { - case *Leaf: - e := newLeaf(n) - if errs := s.Type.resolve(ms.typeDict); errs != nil { - e.Errors = errs - } - if s.Description != nil { - e.Description = s.Description.Name - } - if s.Default != nil { - e.Default = []string{s.Default.Name} - } - e.Type = s.Type.YangType - e.Config, err = tristateValue(s.Config) - e.addError(err) - e.Prefix = getRootPrefix(e) - addExtraKeywordsToLeafEntry(n, e) - e.Mandatory, err = tristateValue(s.Mandatory) - e.addError(err) - return e - case *LeafList: - // Create the equivalent leaf element that we are a list of. - // We can then just annotate it as a list rather than a leaf. - leaf := &Leaf{ - Name: s.Name, - Source: s.Source, - Parent: s.Parent, - Extensions: s.Extensions, - Config: s.Config, - Description: s.Description, - IfFeature: s.IfFeature, - Must: s.Must, - Reference: s.Reference, - Status: s.Status, - Type: s.Type, - Units: s.Units, - When: s.When, - } - - e = ToEntry(leaf) - e.ListAttr = NewDefaultListAttr() - if err := e.ListAttr.parseOrderedBy(s.OrderedBy); err != nil { - e.addError(err) - } - var err error - if e.ListAttr.MaxElements, err = semCheckMaxElements(s.MaxElements); err != nil { - e.addError(err) - } - if e.ListAttr.MinElements, err = semCheckMinElements(s.MinElements); err != nil { - e.addError(err) - } - if len(s.Default) != 0 { - for _, def := range s.Default { - e.Default = append(e.Default, def.Name) - } - } - e.Prefix = getRootPrefix(e) - return e - case *Uses: - g := FindGrouping(s, s.Name, map[string]bool{}) - if g == nil { - return newError(n, "unknown group: %s", s.Name) - } - // We need to return a duplicate so we resolve properly - // when the group is used in multiple locations and the - // grouping has a leafref that references outside the group. - e = ToEntry(g).dup() - addExtraKeywordsToLeafEntry(n, e) - return e - } - - e = newDirectory(n) - - // Special handling for individual Node types. Lists are like any other - // node except a List has a ListAttr. - // - // Nodes of identified special kinds have their Kind set here. - switch s := n.(type) { - case *List: - e.ListAttr = NewDefaultListAttr() - if err := e.ListAttr.parseOrderedBy(s.OrderedBy); err != nil { - e.addError(err) - } - var err error - if e.ListAttr.MaxElements, err = semCheckMaxElements(s.MaxElements); err != nil { - e.addError(err) - } - if e.ListAttr.MinElements, err = semCheckMinElements(s.MinElements); err != nil { - e.addError(err) - } - case *Choice: - e.Kind = ChoiceEntry - if s.Default != nil { - e.Default = []string{s.Default.Name} - } - case *Case: - e.Kind = CaseEntry - case *AnyData: - e.Kind = AnyDataEntry - case *AnyXML: - e.Kind = AnyXMLEntry - case *Input: - e.Kind = InputEntry - case *Output: - e.Kind = OutputEntry - case *Notification: - e.Kind = NotificationEntry - case *Deviate: - e.Kind = DeviateEntry - } - - // Use Elem to get the Value of structure that n is pointing to. - v := reflect.ValueOf(n).Elem() - t := v.Type() - found := false - - for i := t.NumField() - 1; i > 0; i-- { - f := t.Field(i) - yang := f.Tag.Get("yang") - if yang == "" { - continue - } - fv := v.Field(i) - name := strings.Split(yang, ",")[0] - switch name { - case "": - e.addError(fmt.Errorf("%s: nil statement", Source(n))) - case "config": - e.Config, err = tristateValue(fv.Interface()) - e.addError(err) - case "description": - if v := fv.Interface().(*Value); v != nil { - e.Description = v.Name - } - case "prefix": - if v := fv.Interface().(*Value); v != nil { - e.Prefix = v - } - case "action": - for _, r := range fv.Interface().([]*Action) { - e.add(r.Name, ToEntry(r)) - } - case "augment": - for _, a := range fv.Interface().([]*Augment) { - ne := ToEntry(a) - ne.Parent = e - e.Augments = append(e.Augments, ne) - } - case "anydata": - for _, a := range fv.Interface().([]*AnyData) { - e.add(a.Name, ToEntry(a)) - } - case "anyxml": - for _, a := range fv.Interface().([]*AnyXML) { - e.add(a.Name, ToEntry(a)) - } - case "case": - for _, a := range fv.Interface().([]*Case) { - e.add(a.Name, ToEntry(a)) - } - case "choice": - for _, a := range fv.Interface().([]*Choice) { - e.add(a.Name, ToEntry(a)) - } - case "container": - for _, a := range fv.Interface().([]*Container) { - e.add(a.Name, ToEntry(a)) - } - case "grouping": - for _, a := range fv.Interface().([]*Grouping) { - // We just want to parse the grouping to - // collect errors. - e.importErrors(ToEntry(a)) - } - case "import": - // Import only makes types and such available. - // There is nothing else for us to do. - case "include": - for _, a := range fv.Interface().([]*Include) { - // Handle circular dependencies between submodules. This can occur in - // two ways: - // - Where submodule A imports submodule B, and vice versa then the - // whilst processing A we will also try and process A (learnt via - // B). The default case of the switch handles this case. - // - Where submodule A imports submodule B that imports C, which also - // imports A, then we need to check whether we already have merged - // the specified module during this parse attempt. We check this - // against a map of merged submodules. - // The key of the map used is a synthesised value which is formed by - // concatenating the name of this node and the included submodule, - // separated by a ":". - srcToIncluded := a.Module.Name + ":" + n.NName() - includedToSrc := n.NName() + ":" + a.Module.Name - - switch { - case ms.mergedSubmodule[srcToIncluded]: - // We have already merged this module, so don't try and do it - // again. - continue - case !ms.mergedSubmodule[includedToSrc] && a.Module.NName() != n.NName(): - // We have not merged A->B, and B != B hence go ahead and merge. - includedToParent := a.Module.Name + ":" + a.Module.BelongsTo.Name - if ms.mergedSubmodule[includedToParent] { - // Don't try and re-import submodules that have already been imported - // into the top-level module. Note that this ensures that we get to the - // top the tree (whichever the actual module for the chain of - // submodules is). The tracking of the immediate parent is achieved - // through 'key', which ensures that we do not end up in loops - // walking through a sub-cycle of the include graph. - continue - } - ms.mergedSubmodule[srcToIncluded] = true - ms.mergedSubmodule[includedToParent] = true - e.merge(a.Module.Prefix, nil, ToEntry(a.Module)) - case ms.ParseOptions.IgnoreSubmoduleCircularDependencies: - continue - default: - e.addError(fmt.Errorf("%s: has a circular dependency, importing %s", n.NName(), a.Module.NName())) - } - } - case "leaf": - for _, a := range fv.Interface().([]*Leaf) { - e.add(a.Name, ToEntry(a)) - } - case "leaf-list": - for _, a := range fv.Interface().([]*LeafList) { - e.add(a.Name, ToEntry(a)) - } - case "list": - for _, a := range fv.Interface().([]*List) { - e.add(a.Name, ToEntry(a)) - } - case "key": - if v := fv.Interface().(*Value); v != nil { - e.Key = v.Name - } - case "notification": - for _, a := range fv.Interface().([]*Notification) { - e.add(a.Name, ToEntry(a)) - } - case "rpc": - // TODO(borman): what do we do with these? - // seems fine to ignore them for now, we are - // just interested in the tree structure. - for _, r := range fv.Interface().([]*RPC) { - switch rpc := ToEntry(r); { - case rpc.RPC == nil: - // When "rpc" has no "input" or "output" children - rpc.RPC = &RPCEntry{} - fallthrough - default: - e.add(r.Name, rpc) - } - } - - case "input": - if i := fv.Interface().(*Input); i != nil { - if e.RPC == nil { - e.RPC = &RPCEntry{} - } - in := ToEntry(i) - in.Parent = e - e.RPC.Input = in - e.RPC.Input.Name = "input" - e.RPC.Input.Kind = InputEntry - } - case "output": - if o := fv.Interface().(*Output); o != nil { - if e.RPC == nil { - e.RPC = &RPCEntry{} - } - out := ToEntry(o) - out.Parent = e - e.RPC.Output = out - e.RPC.Output.Name = "output" - e.RPC.Output.Kind = OutputEntry - } - case "identity": - if i := fv.Interface().([]*Identity); i != nil { - e.Identities = i - } - case "uses": - for _, a := range fv.Interface().([]*Uses) { - grouping := ToEntry(a) - e.merge(nil, nil, grouping) - // Apply inline augments from the uses statement. Their paths - // are relative to e (the node where the uses appears), so we - // resolve them directly rather than deferring to e.Augment(). - for _, aug := range a.Augment { - target := e.Find(aug.Name) - if target != nil { - augEntry := ToEntry(aug) - target.merge(nil, augEntry.Namespace(), augEntry) - } - } - if ms.ParseOptions.StoreUses { - e.Uses = append(e.Uses, &UsesStmt{a, grouping.shallowDup()}) - } - } - case "type": - // The type keyword is specific to deviate to change a type. Other type handling - // (e.g., leaf type resolution) is done outside of this case. - n, ok := n.(*Deviate) - if !ok { - e.addError(fmt.Errorf("unexpected type found, only valid under Deviate, is %T", n)) - continue - } - - if n.Type != nil { - if errs := n.Type.resolve(ms.typeDict); errs != nil { - e.addError(fmt.Errorf("deviation has unresolvable type, %v", errs)) - continue - } - e.Type = n.Type.YangType - } - continue - // Keywords that do not need to be handled as an Entry as they are added - // to other dictionaries. - case "default": - switch e.Kind { - case LeafEntry, ChoiceEntry: - // default is handled separately for leaf, leaf-list and choice - case DeviateEntry: - // handle deviate statements. - // TODO(wenovus): support refine statement's default substatement. - d, ok := fv.Interface().(*Value) - if !ok { - e.addError(fmt.Errorf("%s: unexpected default type in %s:%s", Source(n), n.Kind(), n.NName())) - } - // TODO(wenovus): deviate statement and refine statement should - // allow multiple default substatements for leaf-list types (YANG1.1). - if d != nil { - e.Default = []string{d.asString()} - } - } - case "typedef": - continue - case "deviation": - if a := fv.Interface().([]*Deviation); a != nil { - for _, d := range a { - deviatedEntry := ToEntry(d) - e.importErrors(deviatedEntry) - e.Deviations = append(e.Deviations, &DeviatedEntry{ - Entry: deviatedEntry, - DeviatedPath: d.Statement().Argument, - }) - - for _, sd := range d.Deviate { - if sd.Type != nil { - sd.Type.resolve(ms.typeDict) - } - } - } - } - case "deviate": - if a := fv.Interface().([]*Deviate); a != nil { - for _, d := range a { - de := ToEntry(d) - - dt, ok := toDeviation[d.Statement().Argument] - if !ok { - e.addError(fmt.Errorf("%s: unknown deviation type in %s:%s", Source(n), n.Kind(), n.NName())) - continue - } - - if e.Deviate == nil { - e.Deviate = map[deviationType][]*Entry{} - } - - e.Deviate[dt] = append(e.Deviate[dt], de) - } - } - case "mandatory": - v, ok := fv.Interface().(*Value) - if !ok { - e.addError(fmt.Errorf("%s: did not get expected value type", Source(n))) - } - e.Mandatory, err = tristateValue(v) - e.addError(err) - case "max-elements", "min-elements": - if e.Kind != DeviateEntry { - continue - } - // we can get max-elements or min-elements in a deviate statement, so create the - // corresponding logic. - v, ok := fv.Interface().(*Value) - if !ok { - e.addError(fmt.Errorf("%s: max or min elements had wrong type, %s:%s", Source(n), n.Kind(), n.NName())) - continue - } - - if e.ListAttr == nil { - e.ListAttr = NewDefaultListAttr() - } - - // Only record the deviation if the statement exists. - if v != nil { - var err error - if name == "max-elements" { - e.deviatePresence.hasMaxElements = true - if e.ListAttr.MaxElements, err = semCheckMaxElements(v); err != nil { - e.addError(err) - } - } else { - e.deviatePresence.hasMinElements = true - if e.ListAttr.MinElements, err = semCheckMinElements(v); err != nil { - e.addError(err) - } - } - } - case "units": - v, ok := fv.Interface().(*Value) - if !ok { - e.addError(fmt.Errorf("%s: units had wrong type, %s:%s", Source(n), n.Kind(), n.NName())) - } - if v != nil { - e.Units = v.asString() - } - // TODO(borman): unimplemented keywords - case "belongs-to", - "contact", - "extension", - "feature", - "if-feature", - "must", - "namespace", - "ordered-by", - "organization", - "presence", - "reference", - "revision", - "status", - "unique", - "when", - "yang-version": - if !fv.IsNil() { - addToExtrasSlice(fv, name, e) - } - continue - - case "Ext", "Name", "Parent", "Statement": - // These are meta-keywords used internally - continue - default: - e.addError(fmt.Errorf("%s: unexpected statement: %s", Source(n), name)) - continue - - } - // We found at least one field. - found = true - } - if !found { - return newError(n, "%T: cannot be converted to a *Entry", n) - } - // If prefix isn't set, provide it based on our root node (module) - if e.Prefix == nil { - e.Prefix = getRootPrefix(e) - } - - return e -} - -// addExtraKeywordsToLeafEntry stores the values for unimplemented keywords in leaf entries. -func addExtraKeywordsToLeafEntry(n Node, e *Entry) { - v := reflect.ValueOf(n).Elem() - t := v.Type() - - for i := t.NumField() - 1; i > 0; i-- { - f := t.Field(i) - yang := f.Tag.Get("yang") - if yang == "" { - continue - } - fv := v.Field(i) - name := strings.Split(yang, ",")[0] - switch name { - case "if-feature", - "must", - "reference", - "status", - "when": - if !fv.IsNil() { - addToExtrasSlice(fv, name, e) - } - } - } -} - -func addToExtrasSlice(fv reflect.Value, name string, e *Entry) { - if fv.Kind() == reflect.Slice { - for j := 0; j < fv.Len(); j++ { - e.Extra[name] = append(e.Extra[name], fv.Index(j).Interface()) - } - } else { - e.Extra[name] = append(e.Extra[name], fv.Interface()) - } -} - -// getRootPrefix returns the prefix of e's root node (module) -func getRootPrefix(e *Entry) *Value { - if m := RootNode(e.Node); m != nil { - return m.getPrefix() - } - return nil -} - -// Augment processes augments in e, return the number of augments processed -// and the augments skipped. If addErrors is true then missing augments will -// generate errors. -func (e *Entry) Augment(addErrors bool) (processed, skipped int) { - // Now process the augments we found - // NOTE(borman): is it possible this will fail if the augment refers - // to some removed sibling that has not been processed? Perhaps this - // should be done after the entire tree is built. Is it correct to - // assume augment paths are data tree paths and not schema tree paths? - // Augments can depend upon augments. We need to figure out how to - // order the augments (or just keep trying until we can make no further - // progress) - var unapplied []*Entry - for _, a := range e.Augments { - target := a.Find(a.Name) - if target == nil { - if addErrors { - e.errorf("%s: augment %s not found", Source(a.Node), a.Name) - } - skipped++ - unapplied = append(unapplied, a) - continue - } - // Augments do not have a prefix we merge in, just a node. - // We retain the namespace from the original context of the - // augment since the nodes have this namespace even though they - // are merged into another entry. - processed++ - target.merge(nil, a.Namespace(), a) - target.Augmented = append(target.Augmented, a.shallowDup()) - } - e.Augments = unapplied - return processed, skipped -} - -// ApplyDeviate walks the deviations within the supplied entry, and applies them to the -// schema. -func (e *Entry) ApplyDeviate(deviateOpts ...DeviateOpt) []error { - var errs []error - appendErr := func(err error) { errs = append(errs, err) } - for _, d := range e.Deviations { - deviatedNode := e.Find(d.DeviatedPath) - if deviatedNode == nil { - appendErr(fmt.Errorf("cannot find target node to deviate, %s", d.DeviatedPath)) - continue - } - - for dt, dv := range d.Deviate { - for _, devSpec := range dv { - switch dt { - case DeviationAdd, DeviationReplace: - if devSpec.Config != TSUnset { - deviatedNode.Config = devSpec.Config - } - - if len(devSpec.Default) > 0 { - switch dt { - case DeviationAdd: - switch { - case deviatedNode.IsLeafList(): - deviatedNode.Default = append(deviatedNode.Default, devSpec.Default...) - case len(devSpec.Default) > 1: - appendErr(fmt.Errorf("%s: tried to add more than one default to a non-leaflist entry at deviation", Source(e.Node))) - case len(deviatedNode.Default) != 0: - appendErr(fmt.Errorf("%s: tried to add a default value to an entry that already has a default value", Source(e.Node))) - case len(devSpec.Default) == 1 && len(deviatedNode.Default) == 0: - deviatedNode.Default = append([]string{}, devSpec.Default[0]) - } - case DeviationReplace: - deviatedNode.Default = append([]string{}, devSpec.Default...) - } - } - - if devSpec.Mandatory != TSUnset { - deviatedNode.Mandatory = devSpec.Mandatory - } - - if devSpec.deviatePresence.hasMinElements { - if !deviatedNode.IsList() && !deviatedNode.IsLeafList() { - appendErr(fmt.Errorf("tried to deviate min-elements on a non-list type %s", deviatedNode.Kind)) - continue - } - deviatedNode.ListAttr.MinElements = devSpec.ListAttr.MinElements - } - - if devSpec.deviatePresence.hasMaxElements { - if !deviatedNode.IsList() && !deviatedNode.IsLeafList() { - appendErr(fmt.Errorf("tried to deviate max-elements on a non-list type %s", deviatedNode.Kind)) - continue - } - deviatedNode.ListAttr.MaxElements = devSpec.ListAttr.MaxElements - } - - if devSpec.Units != "" { - deviatedNode.Units = devSpec.Units - } - - if devSpec.Type != nil { - deviatedNode.Type = devSpec.Type - } - - case DeviationNotSupported: - dp := deviatedNode.Parent - if dp == nil { - appendErr(fmt.Errorf("%s: node %s does not have a valid parent, but deviate not-supported references one", Source(e.Node), e.Name)) - continue - } - if !hasIgnoreDeviateNotSupported(deviateOpts) { - dp.delete(deviatedNode.Name) - } - case DeviationDelete: - if devSpec.Config != TSUnset { - deviatedNode.Config = TSUnset - } - - if len(devSpec.Default) > 0 { - switch { - case deviatedNode.IsLeafList(): - // It is unclear from RFC7950 on how deviate delete works - // when there are duplicate leaf-list values in config-false leafs. - // TODO(wenbli): Add support for deleting default values when the leaf-list is a config leaf (duplicates are not allowed). - appendErr(fmt.Errorf("%s: deviate delete on default statements unsupported for leaf-lists, please use replace instead", Source(e.Node))) - case len(deviatedNode.Default) == 0: - appendErr(fmt.Errorf("%s: tried to deviate delete a default statement that doesn't exist", Source(e.Node))) - case devSpec.Default[0] != deviatedNode.Default[0]: - appendErr(fmt.Errorf("%s: tried to deviate delete a default statement with a non-matching keyword", Source(e.Node))) - default: - deviatedNode.Default = nil - } - } - - if devSpec.Mandatory != TSUnset { - deviatedNode.Mandatory = TSUnset - } - - if devSpec.deviatePresence.hasMinElements { - if !deviatedNode.IsList() && !deviatedNode.IsLeafList() { - appendErr(fmt.Errorf("tried to deviate min-elements on a non-list type %s", deviatedNode.Kind)) - continue - } - if deviatedNode.ListAttr.MinElements != devSpec.ListAttr.MinElements { - // Argument value must match: - // https://tools.ietf.org/html/rfc7950#section-7.20.3.2 - appendErr(fmt.Errorf("min-element value %d differs from deviation's min-element value %d for entry %v", devSpec.ListAttr.MinElements, deviatedNode.ListAttr.MinElements, d.DeviatedPath)) - } - deviatedNode.ListAttr.MinElements = 0 - } - - if devSpec.deviatePresence.hasMaxElements { - if !deviatedNode.IsList() && !deviatedNode.IsLeafList() { - appendErr(fmt.Errorf("tried to deviate max-elements on a non-list type %s", deviatedNode.Kind)) - continue - } - if deviatedNode.ListAttr.MaxElements != devSpec.ListAttr.MaxElements { - appendErr(fmt.Errorf("max-element value %d differs from deviation's max-element value %d for entry %v", devSpec.ListAttr.MaxElements, deviatedNode.ListAttr.MaxElements, d.DeviatedPath)) - } - deviatedNode.ListAttr.MaxElements = math.MaxUint64 - } - - default: - appendErr(fmt.Errorf("invalid deviation type %s", dt)) - } - } - } - } - - return errs -} - -// FixChoice inserts missing Case entries for non-case entries within a choice -// entry. -func (e *Entry) FixChoice() { - if e.Kind == ChoiceEntry && len(e.Errors) == 0 { - for k, ce := range e.Dir { - if ce.Kind != CaseEntry { - ne := &Entry{ - Parent: e, - Node: &Case{ - Parent: ce.Node.ParentNode(), - Name: ce.Node.NName(), - Source: ce.Node.Statement(), - Extensions: ce.Node.Exts(), - }, - Name: ce.Name, - Kind: CaseEntry, - Config: ce.Config, - Prefix: ce.Prefix, - Dir: map[string]*Entry{ce.Name: ce}, - Extra: map[string][]interface{}{}, - } - ce.Parent = ne - e.Dir[k] = ne - } - } - } - for _, ce := range e.Dir { - ce.FixChoice() - } -} - -// ReadOnly returns true if e is a read-only variable (config == false). -// If Config is unset in e, then false is returned if e has no parent, -// otherwise the value parent's ReadOnly is returned. -func (e *Entry) ReadOnly() bool { - switch { - case e == nil: - // We made it all the way to the root of the tree - return false - case e.Kind == OutputEntry: - return true - case e.Config == TSUnset: - return e.Parent.ReadOnly() - default: - return !e.Config.Value() - } -} - -// Find finds the Entry named by name relative to e. -func (e *Entry) Find(name string) *Entry { - if e == nil || name == "" { - return nil - } - parts := strings.Split(name, "/") - - // If parts[0] is "" then this path started with a / - // and we need to find our parent. - if parts[0] == "" { - parts = parts[1:] - contextNode := e.Node - for e.Parent != nil { - e = e.Parent - } - if prefix, _ := getPrefix(parts[0]); prefix != "" { - mod := FindModuleByPrefix(contextNode, prefix) - if mod == nil { - e.addError(fmt.Errorf("cannot find module giving prefix %q within context entry %q", prefix, e.Path())) - return nil - } - m := module(mod) - if m == nil { - e.addError(fmt.Errorf("cannot find which module %q belongs to within context entry %q", - mod.NName(), e.Path())) - return nil - } - if m != e.Node.(*Module) { - e = ToEntry(m) - } - } - } - - for _, part := range parts { - switch { - case e == nil: - return nil - case part == ".": - case part == "..": - e = e.Parent - case e.RPC != nil: - _, part = getPrefix(part) - switch part { - case "input": - if e.RPC.Input == nil { - e.RPC.Input = &Entry{ - Name: "input", - Kind: InputEntry, - Dir: make(map[string]*Entry), - } - } - e = e.RPC.Input - case "output": - if e.RPC.Output == nil { - e.RPC.Output = &Entry{ - Name: "output", - Kind: OutputEntry, - Dir: make(map[string]*Entry), - } - } - e = e.RPC.Output - } - default: - _, part = getPrefix(part) - switch part { - case ".": - case "", "..": - return nil - default: - e = e.Dir[part] - } - } - } - return e -} - -// Path returns the path to e. A nil Entry returns "". -func (e *Entry) Path() string { - if e == nil { - return "" - } - return e.Parent.Path() + "/" + e.Name -} - -// Namespace returns the YANG/XML namespace Value for e as mounted in the Entry -// tree (e.g., as placed by grouping statements). -// -// Per RFC6020 section 7.12, the namespace on elements in the tree due to a -// "uses" statement is that of the where the uses statement occurs, i.e., the -// user, rather than creator (grouping) of those elements, so we follow the -// usage (Entry) tree up to the parent before obtaining the (then adjacent) root -// node for its namespace Value. -func (e *Entry) Namespace() *Value { - // Make e the root parent entry - for ; e.Parent != nil; e = e.Parent { - if e.namespace != nil { - return e.namespace - } - } - - // Return the namespace of a valid root parent entry - if e != nil && e.Node != nil { - if root := RootNode(e.Node); root != nil { - if root.Kind() == "submodule" { - root = root.Modules.Modules[root.BelongsTo.Name] - if root == nil { - return new(Value) - } - } - return root.Namespace - } - } - - // Otherwise return an empty namespace Value (rather than nil) - return new(Value) -} - -// InstantiatingModule returns the YANG module which instantiated the Entry -// within the schema tree - using the same rules described in the documentation -// of the Namespace function. The namespace is resolved in the module name. This -// approach to namespacing is used when serialising YANG-modelled data to JSON as -// per RFC7951. -func (e *Entry) InstantiatingModule() (string, error) { - n := e.Namespace() - if n == nil { - return "", fmt.Errorf("entry %s had nil namespace", e.Name) - } - - module, err := e.Modules().FindModuleByNamespace(n.Name) - if err != nil { - return "", fmt.Errorf("could not find module %q when retrieving namespace for %s: %v", n.Name, e.Name, err) - } - return module.Name, nil -} - -// shallowDup makes a shallow duplicate of e (only direct children are -// duplicated; grandchildren and deeper descendants are deleted). -func (e *Entry) shallowDup() *Entry { - // Warning: if we add any elements to Entry that should not be - // copied we will have to explicitly uncopy them. - ne := *e - - // Now only copy direct children, clear their Dir, and fix up - // Parent pointers. - if e.Dir != nil { - ne.Dir = make(map[string]*Entry, len(e.Dir)) - for k, v := range e.Dir { - de := *v - de.Dir = nil - de.Parent = &ne - ne.Dir[k] = &de - } - } - return &ne -} - -// dup makes a deep duplicate of e. -func (e *Entry) dup() *Entry { - // Warning: if we add any elements to Entry that should not be - // copied we will have to explicitly uncopy them. - // It is possible we may want to do a deep copy on some other fields, - // such as Exts, Choice and Case, but it is not clear that we need - // to do that. - ne := *e - - // Now recurse down to all of our children, fixing up Parent - // pointers as we go. - if e.Dir != nil { - ne.Dir = make(map[string]*Entry, len(e.Dir)) - for k, v := range e.Dir { - de := v.dup() - de.Parent = &ne - ne.Dir[k] = de - } - } - - ne.Extra = make(map[string][]interface{}) - for k, v := range e.Extra { - ne.Extra[k] = v - } - - return &ne -} - -// merge merges a duplicate of oe.Dir into e.Dir, setting the prefix of each -// element to prefix, if not nil. It is an error if e and oe contain common -// elements. -func (e *Entry) merge(prefix *Value, namespace *Value, oe *Entry) { - e.importErrors(oe) - for k, v := range oe.Dir { - v := v.dup() - if prefix != nil { - v.Prefix = prefix - } - if namespace != nil { - v.namespace = namespace - } - if se := e.Dir[k]; se != nil { - er := newError(oe.Node, `Duplicate node %q in %q from: - %s: %s - %s: %s`, k, e.Name, Source(v.Node), v.Name, Source(se.Node), se.Name) - e.addError(er.Errors[0]) - } else { - v.Parent = e - v.Exts = append(v.Exts, oe.Exts...) - for lk := range oe.Extra { - v.Extra[lk] = append(v.Extra[lk], oe.Extra[lk]...) - } - e.Dir[k] = v - } - } -} - -// nless returns -1 if a is less than b, 0 if a == b, and 1 if a > b. -// If a and b are both numeric, then nless compares them as numbers, -// otherwise they are compared lexicographically. -func nless(a, b string) int { - an, ae := strconv.Atoi(a) - bn, be := strconv.Atoi(b) - switch { - case ae == nil && be == nil: - switch { - case an < bn: - return -1 - case an > bn: - return 1 - default: - return 0 - } - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } -} - -type sError struct { - s string - err error -} - -type sortedErrors []sError - -func (s sortedErrors) Len() int { return len(s) } -func (s sortedErrors) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s sortedErrors) Less(i, j int) bool { - // We expect the error strings to be composed of error messages, - // line numbers, etc. delimited by ":". - const errorSplitCount = 4 - fi := strings.SplitN(s[i].s, ":", errorSplitCount) - fj := strings.SplitN(s[j].s, ":", errorSplitCount) - // First, order the errors by the file name. - if fi[0] < fj[0] { - return true - } - if fi[0] > fj[0] { - return false - } - - // compare remaining indices of the error string slices - // in order to create a total ordering. - for i := 1; i < errorSplitCount; i++ { - switch { - // Handle when an expected index doesn't exist. - case len(fj) == i: - return false - case len(fi) == i: - return true - } - - switch nless(fi[i], fj[i]) { - case -1: - return true - case 1: - return false - } - } - return false -} - -// errorSort sorts the strings in the errors slice assuming each line starts -// with file:line:col. Line and column number are sorted numerically. -// Duplicate errors are stripped. -func errorSort(errors []error) []error { - switch len(errors) { - case 0: - return nil - case 1: - return errors - } - elist := make(sortedErrors, len(errors)) - for x, err := range errors { - elist[x] = sError{err.Error(), err} - } - sort.Sort(elist) - errors = make([]error, len(errors)) - i := 0 - for _, err := range elist { - if i > 0 && reflect.DeepEqual(err.err, errors[i-1]) { - continue - } - errors[i] = err.err - i++ - } - return errors[:i] -} - -// SingleDefaultValue returns the single schema default value for e and a bool -// indicating whether the entry contains one and only one default value. The -// empty string is returned when the entry has zero or multiple default values. -// This function is useful for determining the default values of a -// non-leaf-list leaf entry. If the leaf has no explicit default, its type -// default (if any) will be used. -// -// For a leaf-list entry, use DefaultValues() instead. -func (e *Entry) SingleDefaultValue() (string, bool) { - if dvals := e.DefaultValues(); len(dvals) == 1 { - return dvals[0], true - } - return "", false -} - -// DefaultValues returns all default values for the leaf entry. This is useful -// for determining the default values for a leaf-list, which may have more than -// one default value. If the entry has no explicit default, its type default -// (if any) will be used. nil is returned when no default value exists. -// -// For a leaf entry, use SingleDefaultValue() instead. -func (e *Entry) DefaultValues() []string { - if len(e.Default) > 0 { - return append([]string{}, e.Default...) - } - - if typ := e.Type; typ != nil && typ.HasDefault { - switch leaf := e.Node.(type) { - case *Leaf: - switch { - case e.IsLeaf() && (leaf.Mandatory == nil || leaf.Mandatory.Name == "false"), e.IsLeafList() && e.ListAttr.MinElements == 0: - return []string{typ.Default} - } - } - } - return nil -} diff --git a/src/webui/internal/goyang/pkg/yang/entry_test.go b/src/webui/internal/goyang/pkg/yang/entry_test.go deleted file mode 100644 index 2000238b9..000000000 --- a/src/webui/internal/goyang/pkg/yang/entry_test.go +++ /dev/null @@ -1,4141 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "math" - "path/filepath" - "reflect" - "sort" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/openconfig/gnmi/errdiff" -) - -func TestNilEntry(t *testing.T) { - e := ToEntry(nil) - _, ok := e.Node.(*ErrorNode) - if !ok { - t.Fatalf("ToEntry(nil) did not return an error node") - } - errs := e.GetErrors() - switch len(errs) { - default: - t.Errorf("got %d errors, wanted 1", len(errs)) - fallthrough - case 1: - got := errs[0].Error() - want := "ToEntry called on nil AST node" - if got != want { - t.Fatalf("got error %q, want %q", got, want) - } - case 0: - t.Fatalf("GetErrors returned no error") - } -} - -var badInputs = []struct { - name string - in string - errors []string -}{ - { - name: "bad.yang", - in: ` -// Base test yang module. -// This module is syntactally correct (we can build an AST) but it is has -// invalid parameters in many statements. -module base { - namespace "urn:mod"; - prefix "base"; - - container c { - // bad config value in a container - config bad; - } - container d { - leaf bob { - // bad config value - config incorrect; - type unknown; - } - // duplicate leaf entry bob - leaf bob { type string; } - // unknown grouping to uses - uses the-beatles; - } - grouping the-group { - leaf one { type string; } - // duplicate leaf in unused grouping. - leaf one { type int; } - } - uses the-group; -} -`, - errors: []string{ - `bad.yang:9:3: invalid config value: bad`, - `bad.yang:13:3: duplicate key from bad.yang:20:5: bob`, - `bad.yang:14:5: invalid config value: incorrect`, - `bad.yang:17:7: unknown type: base:unknown`, - `bad.yang:22:5: unknown group: the-beatles`, - `bad.yang:24:3: duplicate key from bad.yang:27:5: one`, - }, - }, - { - name: "bad-augment.yang", - in: ` -module base { - namespace "urn:mod"; - prefix "base"; - // augmentation of unknown element - augment erewhon { - leaf bob { - type string; - // bad config value in unused augment - config wrong; - } - } -} -`, - errors: []string{ - `bad-augment.yang:6:3: augment erewhon not found`, - }, - }, - { - name: "bad-min-max-elements.yang", - in: ` -module base { - namespace "urn:mod"; - prefix "base"; - list foo { - // bad arguments to min-elements and max-elements - min-elements bar; - max-elements -5; - } - leaf-list bar { - type string; - // bad arguments to min-elements and max-elements - min-elements unbounded; - max-elements 122222222222222222222222222222222222222222222222222222222222; - } - list baz { - // good arguments - min-elements 0; - max-elements unbounded; - } - list caz { - // bad max element: has to be positive. - min-elements 0; - max-elements 0; - } -} -`, - errors: []string{ - `bad-min-max-elements.yang:7:5: invalid min-elements value`, - `bad-min-max-elements.yang:8:5: invalid max-elements value`, - `bad-min-max-elements.yang:13:5: invalid min-elements value`, - `bad-min-max-elements.yang:14:5: invalid max-elements value`, - `bad-min-max-elements.yang:24:5: invalid max-elements value`, - }, - }, -} - -func TestBadYang(t *testing.T) { - for _, tt := range badInputs { - ms := NewModules() - if err := ms.Parse(tt.in, tt.name); err != nil { - t.Fatalf("unexpected error %s", err) - } - errs := ms.Process() - if len(errs) != len(tt.errors) { - t.Errorf("got %d errors, want %d", len(errs), len(tt.errors)) - } else { - ok := true - for x, err := range errs { - if !strings.Contains(err.Error(), tt.errors[x]) { - ok = false - break - } - } - if ok { - continue - } - } - - var b bytes.Buffer - fmt.Fprint(&b, "got errors:\n") - for _, err := range errs { - fmt.Fprintf(&b, "\t%v\n", err) - } - fmt.Fprint(&b, "want errors:\n") - for _, err := range tt.errors { - fmt.Fprintf(&b, "\t%s\n", err) - } - t.Error(b.String()) - } -} - -var parentTestModules = []struct { - name string - in string -}{ - { - name: "foo.yang", - in: ` -module foo { - namespace "urn:foo"; - prefix "foo"; - - import bar { prefix "temp-bar"; } - container foo-c { - leaf zzz { type string; } - leaf-list foo-list { type string; } - uses temp-bar:common; - } - uses temp-bar:common; -} -`, - }, - { - name: "bar.yang", - in: ` -module bar { - namespace "urn:bar"; - prefix "bar"; - - grouping common { - container test1 { leaf str { type string; } } - container test2 { leaf str { type string; } } - } - - container bar-local { - leaf test1 { type string; } - } - -} -`, - }, - { - name: "baz.yang", - in: ` -module baz { - namespace "urn:baz"; - prefix "baz"; - - import foo { prefix "f"; } - - grouping baz-common { - leaf baz-common-leaf { type string; } - container baz-dir { - leaf aardvark { type string; } - } - } - - augment /f:foo-c { - uses baz-common; - leaf baz-direct-leaf { type string; } - } -} -`, - }, - { - name: "baz-augment.yang", - in: ` - submodule baz-augment { - belongs-to baz { - prefix "baz"; - } - - import foo { prefix "f"; } - - augment "/f:foo-c" { - leaf baz-submod-leaf { type string; } - } - } - `, - }, - { - name: "qux-augment.yang", - in: ` - submodule qux-augment { - belongs-to qux { - prefix "qux"; - } - - import foo { prefix "f"; } - - augment "/f:foo-c" { - leaf qux-submod-leaf { type string; } - } - } - `, - }, -} - -func TestUsesParent(t *testing.T) { - ms := NewModules() - for _, tt := range parentTestModules { - _ = ms.Parse(tt.in, tt.name) - } - - efoo, _ := ms.GetModule("foo") - used := efoo.Dir["foo-c"].Dir["test1"] - expected := "/foo/foo-c/test1" - if used.Path() != expected { - t.Errorf("want %s, got %s", expected, used.Path()) - } - - used = efoo.Dir["test1"] - expected = "/foo/test1" - if used.Path() != expected { - t.Errorf("want %s, got %s", expected, used.Path()) - } -} - -func TestPrefixes(t *testing.T) { - ms := NewModules() - for _, tt := range parentTestModules { - _ = ms.Parse(tt.in, tt.name) - } - - efoo, _ := ms.GetModule("foo") - if efoo.Prefix.Name != "foo" { - t.Errorf(`want prefix "foo", got %q`, efoo.Prefix.Name) - } - - used := efoo.Dir["foo-c"].Dir["zzz"] - if used.Prefix == nil || used.Prefix.Name != "foo" { - t.Errorf(`want prefix named "foo", got %#v`, used.Prefix) - } - - used = efoo.Dir["foo-c"].Dir["foo-list"] - if used.Prefix == nil || used.Prefix.Name != "foo" { - t.Errorf(`want prefix named "foo", got %#v`, used.Prefix) - } - used = efoo.Dir["foo-c"].Dir["test1"] - if used.Prefix.Name != "bar" { - t.Errorf(`want prefix "bar", got %q`, used.Prefix.Name) - } - - used = efoo.Dir["foo-c"].Dir["test1"].Dir["str"] - if used.Prefix == nil || used.Prefix.Name != "bar" { - t.Errorf(`want prefix named "bar", got %#v`, used.Prefix) - } - -} - -func TestEntryNamespace(t *testing.T) { - ms := NewModules() - for _, tt := range parentTestModules { - if err := ms.Parse(tt.in, tt.name); err != nil { - t.Fatalf("could not parse module %s: %v", tt.name, err) - } - } - - if errs := ms.Process(); len(errs) > 0 { - t.Fatalf("could not process modules: %v", errs) - } - - foo, _ := ms.GetModule("foo") - bar, _ := ms.GetModule("bar") - - for _, tc := range []struct { - descr string - entry *Entry - ns string - wantMod string - wantModError string - }{ - { - descr: "grouping used in foo always have foo's namespace, even if it was defined in bar", - entry: foo.Dir["foo-c"].Dir["test1"], - ns: "urn:foo", - wantMod: "foo", - }, - { - descr: "grouping defined and used in foo has foo's namespace", - entry: foo.Dir["foo-c"].Dir["zzz"], - ns: "urn:foo", - wantMod: "foo", - }, - { - descr: "grouping defined and used in bar has bar's namespace", - entry: bar.Dir["bar-local"].Dir["test1"], - ns: "urn:bar", - wantMod: "bar", - }, - { - descr: "leaf within a used grouping in baz augmented into foo has baz's namespace", - entry: foo.Dir["foo-c"].Dir["baz-common-leaf"], - ns: "urn:baz", - wantMod: "baz", - }, - { - descr: "leaf directly defined within an augment to foo from baz has baz's namespace", - entry: foo.Dir["foo-c"].Dir["baz-direct-leaf"], - ns: "urn:baz", - wantMod: "baz", - }, - { - descr: "leaf directly defined within an augment to foo from submodule baz-augment of baz has baz's namespace", - entry: foo.Dir["foo-c"].Dir["baz-submod-leaf"], - ns: "urn:baz", - wantMod: "baz", - }, - { - descr: "leaf directly defined within an augment to foo from orphan submodule qux-augment has empty namespace", - entry: foo.Dir["foo-c"].Dir["qux-submod-leaf"], - ns: "", - wantModError: `could not find module "" when retrieving namespace for qux-submod-leaf: "": no such namespace`, - }, - { - descr: "children of a container within an augment to from baz have baz's namespace", - entry: foo.Dir["foo-c"].Dir["baz-dir"].Dir["aardvark"], - ns: "urn:baz", - wantMod: "baz", - }, - } { - nsValue := tc.entry.Namespace() - if nsValue == nil { - t.Errorf("%s: want namespace %s, got nil", tc.descr, tc.ns) - } else if tc.ns != nsValue.Name { - t.Errorf("%s: want namespace %s, got %s", tc.descr, tc.ns, nsValue.Name) - } - - m, err := tc.entry.InstantiatingModule() - if err != nil { - if tc.wantModError == "" { - t.Errorf("%s: %s.InstantiatingModule(): got unexpected error: %v", tc.descr, tc.entry.Path(), err) - } else if got := err.Error(); got != tc.wantModError { - t.Errorf("%s: %s.InstantiatingModule(): got error: %q, want: %q", tc.descr, tc.entry.Path(), got, tc.wantModError) - } - continue - } else if tc.wantModError != "" { - t.Errorf("%s: %s.InstantiatingModule(): got no error, want: %q", tc.descr, tc.entry.Path(), tc.wantModError) - continue - } - - if m != tc.wantMod { - t.Errorf("%s: %s.InstantiatingModule(): did not get expected name, got: %v, want: %v", - tc.descr, tc.entry.Path(), m, tc.wantMod) - } - } -} - -var testWhenModules = []struct { - name string - in string -}{ - { - name: "when.yang", - in: ` -module when { - namespace "urn:when"; - prefix "when"; - - leaf condition { type string; } - - container alpha { - when "../condition = 'alpha'"; - } - - leaf beta { - when "../condition = 'beta'"; - type string; - } - - leaf-list gamma { - when "../condition = 'gamma'"; - type string; - } - - list delta { - when "../condition = 'delta'"; - } - - choice epsilon { - when "../condition = 'epsilon'"; - - case zeta { - when "../condition = 'zeta'"; - } - } - - anyxml eta { - when "../condition = 'eta'"; - } - - anydata theta { - when "../condition = 'theta'"; - } - - uses iota { - when "../condition = 'iota'"; - } - - grouping iota { - } - - augment "../alpha" { - when "../condition = 'kappa'"; - } -} -`, - }, -} - -func TestGetWhenXPath(t *testing.T) { - ms := NewModules() - ms.ParseOptions.StoreUses = true - for _, tt := range testWhenModules { - if err := ms.Parse(tt.in, tt.name); err != nil { - t.Fatalf("could not parse module %s: %v", tt.name, err) - } - } - - if errs := ms.Process(); len(errs) > 0 { - t.Fatalf("could not process modules: %v", errs) - } - - when, _ := ms.GetModule("when") - - testcases := []struct { - descr string - childName string - isCase bool - choiceName string - isAugment bool - augmentTarget string - }{ - { - descr: "extract when statement from *Container", - childName: "alpha", - }, { - descr: "extract when statement from *Leaf", - childName: "beta", - }, { - descr: "extract when statement from *LeafList", - childName: "gamma", - }, { - descr: "extract when statement from *List", - childName: "delta", - }, { - descr: "extract when statement from *Choice", - childName: "epsilon", - }, { - descr: "extract when statement from *Case", - childName: "zeta", - isCase: true, - choiceName: "epsilon", - }, { - descr: "extract when statement from *AnyXML", - childName: "eta", - }, { - descr: "extract when statement from *AnyData", - childName: "theta", - }, { - descr: "extract when statement from *Augment", - childName: "kappa", - isAugment: true, - augmentTarget: "alpha", - }, - } - - for _, tc := range testcases { - parentEntry := when - t.Run(tc.descr, func(t *testing.T) { - var child *Entry - - if tc.isAugment { - child = parentEntry.Dir[tc.augmentTarget].Augmented[0] - } else { - if tc.isCase { - parentEntry = parentEntry.Dir[tc.choiceName] - } - child = parentEntry.Dir[tc.childName] - } - - expectedWhen := "../condition = '" + tc.childName + "'" - - if gotWhen, ok := child.GetWhenXPath(); !ok { - t.Errorf("Cannot get when statement of child entry %v", tc.childName) - } else if gotWhen != expectedWhen { - t.Errorf("Expected when XPath %v, but got %v", expectedWhen, gotWhen) - } - }) - } -} - -var testAugmentAndUsesModules = []struct { - name string - in string -}{ - { - name: "original.yang", - in: ` -module original { - namespace "urn:original"; - prefix "orig"; - - import groupings { - prefix grp; - } - - container alpha { - leaf beta { - type string; - } - leaf psi { - type string; - } - leaf omega { - type string; - } - uses grp:nestedLevel0 { - when "beta = 'holaWorld'"; - } - } -} -`, - }, - { - name: "augments.yang", - in: ` -module augments { - namespace "urn:augments"; - prefix "aug"; - - import original { - prefix orig; - } - - import groupings { - prefix grp; - } - - augment "/orig:alpha" { - when "orig:beta = 'helloWorld'"; - - container charlie { - leaf charlieLeaf { - type string; - } - } - } - - grouping delta { - container echo { - leaf echoLeaf { - type string; - } - } - } - - augment "/orig:alpha" { - when "orig:omega = 'privetWorld'"; - uses delta { - when "current()/orig:beta = 'nihaoWorld'"; - } - } -} -`, - }, - { - name: "groupings.yang", - in: ` -module groupings { - namespace "urn:groupings"; - prefix "grp"; - - import "original" { - prefix orig; - } - - grouping nestedLevel0 { - leaf leafAtLevel0 { - type string; - } - uses nestedLevel1 { - when "orig:psi = 'geiasouWorld'"; - } - } - - grouping nestedLevel1 { - leaf leafAtLevel1 { - type string; - } - uses nestedLevel2 { - when "orig:omega = 'salveWorld'"; - } - } - - grouping nestedLevel2 { - leaf leafAtLevel2 { - type string; - } - } -} -`, - }, -} - -func TestAugmentedEntry(t *testing.T) { - ms := NewModules() - for _, tt := range testAugmentAndUsesModules { - if err := ms.Parse(tt.in, tt.name); err != nil { - t.Fatalf("could not parse module %s: %v", tt.name, err) - } - } - - if errs := ms.Process(); len(errs) > 0 { - t.Fatalf("could not process modules: %v", errs) - } - - orig, _ := ms.GetModule("original") - - testcases := []struct { - descr string - augmentEntry *Entry - augmentWhenStmt string - augmentChildNames map[string]bool - }{ - { - descr: "leaf charlie is augmented to container alpha", - augmentEntry: orig.Dir["alpha"].Augmented[0], - augmentWhenStmt: "orig:beta = 'helloWorld'", - augmentChildNames: map[string]bool{ - "charlie": false, - }, - }, { - descr: "grouping delta is augmented to container alpha", - augmentEntry: orig.Dir["alpha"].Augmented[1], - augmentWhenStmt: "orig:omega = 'privetWorld'", - augmentChildNames: map[string]bool{ - "echo": false, - }, - }, - } - - for _, tc := range testcases { - t.Run(tc.descr, func(t *testing.T) { - augment := tc.augmentEntry - - if tc.augmentWhenStmt != "" { - if gotAugmentWhenStmt, ok := augment.GetWhenXPath(); !ok { - t.Errorf("Expected augment when statement %v, but not present", - tc.augmentWhenStmt) - } else if gotAugmentWhenStmt != tc.augmentWhenStmt { - t.Errorf("Expected augment when statement %v, but got %v", - tc.augmentWhenStmt, gotAugmentWhenStmt) - } - } - - for name, entry := range augment.Dir { - if _, ok := tc.augmentChildNames[name]; ok { - tc.augmentChildNames[name] = true - } else { - t.Errorf("Got unexpected child name %v in augment", name) - } - - if entry.Dir != nil { - t.Errorf("Expected augment's child entry %v have nil dir, but got %v", - name, entry.Dir) - } - } - - for name, matched := range tc.augmentChildNames { - if !matched { - t.Errorf("Expected child name %v in augment, but not present", name) - } - } - - }) - } -} - -func TestUsesEntry(t *testing.T) { - ms := NewModules() - ms.ParseOptions.StoreUses = true - for _, tt := range testAugmentAndUsesModules { - if err := ms.Parse(tt.in, tt.name); err != nil { - t.Fatalf("could not parse module %s: %v", tt.name, err) - } - } - - if errs := ms.Process(); len(errs) > 0 { - t.Fatalf("could not process modules: %v", errs) - } - - orig, _ := ms.GetModule("original") - - testcases := []struct { - descr string - usesParentEntry *Entry - usesWhenStmts []string - groupingChildNames []map[string]bool - nestedLevel int - }{ - { - descr: "second augment in augments.yang uses grouping delta", - usesParentEntry: orig.Dir["alpha"].Augmented[1], - usesWhenStmts: []string{"current()/orig:beta = 'nihaoWorld'"}, - groupingChildNames: []map[string]bool{{"echo": false}}, - }, { - descr: "container alpha uses nested grouping nestedLevel0", - usesParentEntry: orig.Dir["alpha"], - usesWhenStmts: []string{ - "beta = 'holaWorld'", - "orig:psi = 'geiasouWorld'", - "orig:omega = 'salveWorld'", - }, - groupingChildNames: []map[string]bool{ - {"leafAtLevel0": false, "leafAtLevel1": false, "leafAtLevel2": false}, - {"leafAtLevel1": false, "leafAtLevel2": false}, - {"leafAtLevel2": false}, - }, - nestedLevel: 2, - }, - } - - for _, tc := range testcases { - t.Run(tc.descr, func(t *testing.T) { - usesParentEntry := tc.usesParentEntry - for i := 0; i <= tc.nestedLevel; i++ { - usesStmts := usesParentEntry.Uses - // want the usesStmts to have length 1, otherwise also need to verify - // every usesStmt slice element is expected. - if len(usesStmts) != 1 { - t.Errorf("Expected usesStmts to have length 1, but got %v", - len(usesStmts)) - } - - usesNode := usesStmts[0].Uses - grouping := usesStmts[0].Grouping - - if tc.usesWhenStmts[i] != "" { - if gotUsesWhenStmt, ok := usesNode.When.Statement().Arg(); !ok { - t.Errorf("Expected uses when statement %v, but not present", - tc.usesWhenStmts[i]) - } else if gotUsesWhenStmt != tc.usesWhenStmts[i] { - t.Errorf("Expected uses when statement %v, but got %v", - tc.usesWhenStmts[i], gotUsesWhenStmt) - } - } - - for name, entry := range grouping.Dir { - if _, ok := tc.groupingChildNames[i][name]; ok { - tc.groupingChildNames[i][name] = true - } else { - t.Errorf("Got unexpected child name %v in uses", name) - } - - if entry.Dir != nil { - t.Errorf("Expected uses's child entry %v have nil dir, but got %v", - name, entry.Dir) - } - } - - for name, matched := range tc.groupingChildNames[i] { - if !matched { - t.Errorf("Expected child name %v in grouping %v, but not present", - name, grouping.Name) - } - } - usesParentEntry = grouping - } - - }) - } -} - -func TestShallowDup(t *testing.T) { - testModule := struct { - name string - in string - }{ - - name: "mod.yang", - in: ` -module mod { - namespace "urn:mod"; - prefix "mod"; - - container level0 { - container level1-1 { - leaf level2-1 { type string;} - } - - container level1-2 { - leaf level2-2 { type string;} - } - - container level1-3{ - container level2-3 { - leaf level3-1 { type string;} - } - } - } -} -`, - } - - ms := NewModules() - - if err := ms.Parse(testModule.in, testModule.name); err != nil { - t.Fatalf("could not parse module %s: %v", testModule.name, err) - } - - if errs := ms.Process(); len(errs) > 0 { - t.Fatalf("could not process modules: %v", errs) - } - - mod, _ := ms.GetModule("mod") - level0 := mod.Dir["level0"] - level0ShallowDup := level0.shallowDup() - - for name, entry := range level0.Dir { - shallowDupedEntry, ok := level0ShallowDup.Dir[name] - if !ok { - t.Errorf("Expect shallowDup() to duplicate direct child %v, but did not", name) - } - if len(entry.Dir) != 1 { - t.Errorf("Expect original entry's direct child have length 1 dir") - } - if shallowDupedEntry.Dir != nil { - t.Errorf("Expect shallowDup()'ed entry's direct child to have nil dir") - } - } -} - -func TestIgnoreCircularDependencies(t *testing.T) { - tests := []struct { - name string - inModules map[string]string - inIgnoreCircDep bool - wantErrs bool - }{{ - name: "validation that non-circular dependencies are correct", - inModules: map[string]string{ - "mod-a": ` - module mod-a { - namespace "urn:a"; - prefix "a"; - - include subm-x; - include subm-y; - - leaf marker { type string; } - } - `, - "subm-x": ` - submodule subm-x { - belongs-to mod-a { prefix a; } - } - `, - "subm-y": ` - submodule subm-y { - belongs-to mod-a { prefix a; } - // Not circular. - include subm-x; - } - `}, - }, { - name: "circular dependency error identified", - inModules: map[string]string{ - "mod-a": ` - module mod-a { - namespace "urn:a"; - prefix "a"; - - include subm-x; - include subm-y; - - leaf marker { type string; } - } - `, - "subm-x": ` - submodule subm-x { - belongs-to mod-a { prefix a; } - // Circular - include subm-y; - } - `, - "subm-y": ` - submodule subm-y { - belongs-to mod-a { prefix a; } - // Circular - include subm-x; - } - `}, - wantErrs: true, - }, { - name: "circular dependency error skipped", - inModules: map[string]string{ - "mod-a": ` - module mod-a { - namespace "urn:a"; - prefix "a"; - - include subm-x; - include subm-y; - - leaf marker { type string; } - } - `, - "subm-x": ` - submodule subm-x { - belongs-to mod-a { prefix a; } - // Circular - include subm-y; - } - `, - "subm-y": ` - submodule subm-y { - belongs-to mod-a { prefix a; } - // Circular - include subm-x; - } - `}, - inIgnoreCircDep: true, - }} - - for _, tt := range tests { - ms := NewModules() - ms.ParseOptions.IgnoreSubmoduleCircularDependencies = tt.inIgnoreCircDep - for n, m := range tt.inModules { - if err := ms.Parse(m, n); err != nil { - if !tt.wantErrs { - t.Errorf("%s: could not parse modules, got: %v, want: nil", tt.name, err) - } - continue - } - } - } -} - -func TestEntryDefaultValue(t *testing.T) { - getdir := func(e *Entry, elements ...string) (*Entry, error) { - for _, elem := range elements { - next := e.Dir[elem] - if next == nil { - return nil, fmt.Errorf("%s missing directory %q", e.Path(), elem) - } - e = next - } - return e, nil - } - - modtext := ` -module defaults { - namespace "urn:defaults"; - prefix "defaults"; - - typedef string-default { - type string; - default "typedef default value"; - } - - typedef string-emptydefault { - type string; - default ""; - } - - grouping common { - container common-nodefault { - leaf string { - type string; - } - } - container common-withdefault { - leaf string { - type string; - default "default value"; - } - } - container common-withemptydefault { - leaf string { - type string; - default ""; - } - } - container common-typedef-withdefault { - leaf string { - type string-default; - } - } - container common-typedef-withemptydefault { - leaf string { - type string-emptydefault; - } - } - } - - container defaults { - leaf mandatory-default { - type string-default; - mandatory true; - } - leaf uint32-withdefault { - type uint32; - default 13; - } - leaf string-withdefault { - type string-default; - } - leaf nodefault { - type string; - } - uses common; - - choice choice-default { - case alpha { - leaf alpha { - type string; - } - } - case zeta { - leaf zeta { - type string; - } - } - default zeta; - } - } - - grouping leaflist-common { - container common-nodefault { - leaf string { - type string; - } - } - container common-withdefault { - leaf-list string { - type string; - default "default value"; - } - } - container common-typedef-withdefault { - leaf string { - type string-default; - } - } - } - - container leaflist-defaults { - leaf-list uint32-withdefault { - type uint32; - default "13"; - default 14; - } - leaf-list stringlist-withdefault { - type string-default; - } - leaf-list stringlist-withemptydefault { - type string-emptydefault; - } - leaf-list stringlist-withdefault-withminelem { - type string-default; - min-elements 1; - } - leaf-list emptydefault { - type string; - default ""; - } - leaf-list nodefault { - type string; - } - uses leaflist-common; - } - -} -` - - ms := NewModules() - if err := ms.Parse(modtext, "defaults.yang"); err != nil { - t.Fatal(err) - } - - for i, tc := range []struct { - wantSingle string - wantSingleOk bool - wantDefaults []string - path []string - }{ - { - path: []string{"defaults", "string-withdefault"}, - wantSingle: "typedef default value", - wantDefaults: []string{"typedef default value"}, - wantSingleOk: true, - }, - { - path: []string{"defaults", "uint32-withdefault"}, - wantSingle: "13", - wantDefaults: []string{"13"}, - wantSingleOk: true, - }, - { - path: []string{"defaults", "nodefault"}, - wantSingle: "", - wantDefaults: nil, - }, - { - path: []string{"defaults", "common-withdefault", "string"}, - wantSingle: "default value", - wantDefaults: []string{"default value"}, - wantSingleOk: true, - }, - { - path: []string{"defaults", "common-withemptydefault", "string"}, - wantSingle: "", - wantDefaults: []string{""}, - wantSingleOk: true, - }, - { - path: []string{"defaults", "common-typedef-withdefault", "string"}, - wantSingle: "typedef default value", - wantDefaults: []string{"typedef default value"}, - wantSingleOk: true, - }, - { - path: []string{"defaults", "common-typedef-withemptydefault", "string"}, - wantSingle: "", - wantDefaults: []string{""}, - wantSingleOk: true, - }, - { - path: []string{"defaults", "common-nodefault", "string"}, - wantSingle: "", - wantDefaults: nil, - }, - { - path: []string{"defaults", "mandatory-default"}, - wantSingle: "", - wantDefaults: nil, - }, - { - path: []string{"defaults", "choice-default"}, - wantSingle: "zeta", - wantDefaults: []string{"zeta"}, - wantSingleOk: true, - }, - { - path: []string{"leaflist-defaults", "uint32-withdefault"}, - wantSingle: "", - wantDefaults: []string{"13", "14"}, - }, - { - path: []string{"leaflist-defaults", "stringlist-withdefault"}, - wantSingle: "typedef default value", - wantDefaults: []string{"typedef default value"}, - wantSingleOk: true, - }, - { - path: []string{"leaflist-defaults", "stringlist-withemptydefault"}, - wantSingle: "", - wantDefaults: []string{""}, - wantSingleOk: true, - }, - { - path: []string{"leaflist-defaults", "stringlist-withdefault-withminelem"}, - wantSingle: "", - wantDefaults: nil, - }, - { - path: []string{"leaflist-defaults", "emptydefault"}, - wantSingle: "", - wantDefaults: []string{""}, - wantSingleOk: true, - }, - { - path: []string{"leaflist-defaults", "nodefault"}, - wantSingle: "", - wantDefaults: nil, - }, - { - path: []string{"leaflist-defaults", "common-nodefault", "string"}, - wantSingle: "", - wantDefaults: nil, - }, - { - path: []string{"leaflist-defaults", "common-withdefault", "string"}, - wantSingle: "default value", - wantDefaults: []string{"default value"}, - wantSingleOk: true, - }, - { - path: []string{"leaflist-defaults", "common-typedef-withdefault", "string"}, - wantSingle: "typedef default value", - wantDefaults: []string{"typedef default value"}, - wantSingleOk: true, - }, - } { - tname := strings.Join(tc.path, "/") - - mod, ok := ms.Modules["defaults"] - if !ok { - t.Fatalf("[%d] module not found: %q", i, tname) - } - defaults := ToEntry(mod) - dir, err := getdir(defaults, tc.path...) - if err != nil { - t.Fatalf("[%d_%s] could not retrieve path: %v", i, tname, err) - } - if got, gotOk := dir.SingleDefaultValue(); got != tc.wantSingle || gotOk != tc.wantSingleOk { - t.Errorf("[%d_%s] got SingleDefaultValue (%q, %v), want (%q, %v)", i, tname, got, gotOk, tc.wantSingle, tc.wantSingleOk) - } - if diff := cmp.Diff(dir.DefaultValues(), tc.wantDefaults); diff != "" { - t.Errorf("[%d_%s] DefaultValues (-got, +want):\n%s", i, tname, diff) - } - } -} - -func TestFullModuleProcess(t *testing.T) { - tests := []struct { - name string - inModules map[string]string - inIgnoreCircDeps bool - wantLeaves map[string][]string - customVerify func(t *testing.T, module *Entry) - wantErr bool - }{{ - name: "circular import via child", - inModules: map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - include test-router; - include test-router-bgp; - include test-router-isis; - - container configure { - uses test-router; - } - }`, - "test-router": ` - submodule test-router { - belongs-to test { prefix "t"; } - - include test-router-bgp; - include test-router-isis; - include test-router-ldp; - - grouping test-router { - uses test-router-ldp; - } - }`, - "test-router-ldp": ` - submodule test-router-ldp { - belongs-to test { prefix "t"; } - - grouping test-router-ldp { } - }`, - "test-router-isis": ` - submodule test-router-isis { - belongs-to test { prefix "t"; } - - include test-router; - }`, - "test-router-bgp": ` - submodule test-router-bgp { - belongs-to test { prefix "t"; } - }`, - }, - inIgnoreCircDeps: true, - }, { - name: "non-circular via child", - inModules: map[string]string{ - "bgp": ` - module bgp { - prefix "bgp"; - namespace "urn:bgp"; - - include bgp-son; - include bgp-daughter; - - leaf parent { type string; } - }`, - "bgp-son": ` - submodule bgp-son { - belongs-to bgp { prefix "bgp"; } - - leaf son { type string; } - }`, - "bgp-daughter": ` - submodule bgp-daughter { - belongs-to bgp { prefix "bgp"; } - include bgp-son; - - leaf daughter { type string; } - }`, - }, - }, { - name: "simple circular via child", - inModules: map[string]string{ - "parent": ` - module parent { - prefix "p"; - namespace "urn:p"; - include son; - include daughter; - - leaf p { type string; } - } - `, - "son": ` - submodule son { - belongs-to parent { prefix "p"; } - include daughter; - - leaf s { type string; } - } - `, - "daughter": ` - submodule daughter { - belongs-to parent { prefix "p"; } - include son; - - leaf d { type string; } - } - `, - }, - wantErr: true, - }, { - name: "merge via grandchild", - inModules: map[string]string{ - "bgp": ` - module bgp { - prefix "bgp"; - namespace "urn:bgp"; - - include bgp-son; - - leaf parent { type string; } - }`, - "bgp-son": ` - submodule bgp-son { - belongs-to bgp { prefix "bgp"; } - - include bgp-grandson; - - leaf son { type string; } - }`, - "bgp-grandson": ` - submodule bgp-grandson { - belongs-to bgp { prefix "bgp"; } - - leaf grandson { type string; } - }`, - }, - wantLeaves: map[string][]string{ - "bgp": {"parent", "son", "grandson"}, - }, - }, { - name: "parent to son and daughter with common grandchild", - inModules: map[string]string{ - "parent": ` - module parent { - prefix "p"; - namespace "urn:p"; - include son; - include daughter; - - leaf p { type string; } - } - `, - "son": ` - submodule son { - belongs-to parent { prefix "p"; } - include grandchild; - - leaf s { type string; } - } - `, - "daughter": ` - submodule daughter { - belongs-to parent { prefix "p"; } - include grandchild; - - leaf d { type string; } - } - `, - "grandchild": ` - submodule grandchild { - belongs-to parent { prefix "p"; } - - leaf g { type string; } - } - `, - }, - wantLeaves: map[string][]string{ - "parent": {"p", "s", "d", "g"}, - }, - }, { - name: "parent to son and daughter, not a circdep", - inModules: map[string]string{ - "parent": ` - module parent { - prefix "p"; - namespace "urn:p"; - - include son; - include daughter; - - uses son-group; - } - `, - "son": ` - submodule son { - belongs-to parent { prefix "p"; } - include daughter; - - grouping son-group { - uses daughter-group; - } - } - `, - "daughter": ` - submodule daughter { - belongs-to parent { prefix "p"; } - - grouping daughter-group { - leaf s { type string; } - } - - leaf d { type string; } - } - `, - }, - wantLeaves: map[string][]string{ - "parent": {"s", "d"}, - }, - }, { - name: "parent with grouping and with extension", - inModules: map[string]string{ - "parent": ` - module parent { - prefix "p"; - namespace "urn:p"; - - import extensions { - prefix "ext"; - } - - container c { - ext:c-define "c's extension"; - uses daughter-group { - ext:u-define "uses's extension"; - } - } - - grouping daughter-group { - ext:g-define "daughter-group's extension"; - - leaf l { - ext:l-define "l's extension"; - type string; - } - - container c2 { - leaf l2 { - type string; - } - } - - // test nested grouping extensions. - uses son-group { - ext:sg-define "son-group's extension"; - } - } - - grouping son-group { - leaf s { - ext:s-define "s's extension"; - type string; - } - - } - } - `, - "extension": ` - module extensions { - prefix "q"; - namespace "urn:q"; - - extension c-define { - description - "Takes as an argument a name string. - c's extension."; - argument "name"; - } - extension g-define { - description - "Takes as an argument a name string. - daughter-group's extension."; - argument "name"; - } - extension sg-define { - description - "Takes as an argument a name string. - son-groups's extension."; - argument "name"; - } - extension s-define { - description - "Takes as an argument a name string. - s's extension."; - argument "name"; - } - extension l-define { - description - "Takes as an argument a name string. - l's extension."; - argument "name"; - } - extension u-define { - description - "Takes as an argument a name string. - uses's extension."; - argument "name"; - } - } - `, - }, - wantLeaves: map[string][]string{ - "parent": {"c"}, - }, - customVerify: func(t *testing.T, module *Entry) { - // Verify that an extension within the uses statement - // and within a grouping's definition is copied to each - // of the top-level nodes within the grouping, and no - // one else above or below. - less := cmpopts.SortSlices(func(l, r *Statement) bool { return l.Keyword < r.Keyword }) - - if diff := cmp.Diff([]*Statement{ - {Keyword: "ext:c-define", HasArgument: true, Argument: "c's extension"}, - }, module.Dir["c"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { - t.Errorf("container c Exts (-want, +got):\n%s", diff) - } - - if diff := cmp.Diff([]*Statement{ - {Keyword: "ext:g-define", HasArgument: true, Argument: "daughter-group's extension"}, - {Keyword: "ext:l-define", HasArgument: true, Argument: "l's extension"}, - {Keyword: "ext:u-define", HasArgument: true, Argument: "uses's extension"}, - }, module.Dir["c"].Dir["l"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { - t.Errorf("leaf l Exts (-want, +got):\n%s", diff) - } - - if diff := cmp.Diff([]*Statement{ - {Keyword: "ext:g-define", HasArgument: true, Argument: "daughter-group's extension"}, - {Keyword: "ext:sg-define", HasArgument: true, Argument: "son-group's extension"}, - {Keyword: "ext:s-define", HasArgument: true, Argument: "s's extension"}, - {Keyword: "ext:u-define", HasArgument: true, Argument: "uses's extension"}, - }, module.Dir["c"].Dir["s"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { - t.Errorf("leaf s Exts (-want, +got):\n%s", diff) - } - - if diff := cmp.Diff([]*Statement{ - {Keyword: "ext:g-define", HasArgument: true, Argument: "daughter-group's extension"}, - {Keyword: "ext:u-define", HasArgument: true, Argument: "uses's extension"}, - }, module.Dir["c"].Dir["c2"].Exts, cmpopts.IgnoreUnexported(Statement{}), less); diff != "" { - t.Errorf("container c2 Exts (-want, +got):\n%s", diff) - } - - if diff := cmp.Diff([]*Statement{}, module.Dir["c"].Dir["c2"].Dir["l2"].Exts, cmpopts.IgnoreUnexported(Statement{}), less, cmpopts.EquateEmpty()); diff != "" { - t.Errorf("leaf l2 Exts (-want, +got):\n%s", diff) - } - }, - }} - - for _, tt := range tests { - ms := NewModules() - - ms.ParseOptions.IgnoreSubmoduleCircularDependencies = tt.inIgnoreCircDeps - for n, m := range tt.inModules { - if err := ms.Parse(m, n); err != nil { - t.Errorf("%s: error parsing module %s, got: %v, want: nil", tt.name, n, err) - } - } - - if errs := ms.Process(); len(errs) > 0 { - if !tt.wantErr { - t.Errorf("%s: error processing modules, got: %v, want: nil", tt.name, errs) - } - continue - } - - if tt.wantErr { - t.Errorf("%s: did not get expected errors", tt.name) - continue - } - - for m, l := range tt.wantLeaves { - mod, errs := ms.GetModule(m) - if len(errs) > 0 { - t.Errorf("%s: cannot retrieve expected module %s, got: %v, want: nil", tt.name, m, errs) - continue - } - - var leaves []string - for _, n := range mod.Dir { - leaves = append(leaves, n.Name) - } - - // Sort the two slices to ensure that we are comparing like with like. - sort.Strings(l) - sort.Strings(leaves) - if !reflect.DeepEqual(l, leaves) { - t.Errorf("%s: did not get expected leaves in %s, got: %v, want: %v", tt.name, m, leaves, l) - } - - if tt.customVerify != nil { - tt.customVerify(t, mod) - } - } - } -} - -func TestAnyDataAnyXML(t *testing.T) { - tests := []struct { - name string - inModule string - wantNodeKind string - wantEntryKind EntryKind - }{ - { - name: "test anyxml", - wantNodeKind: "anyxml", - wantEntryKind: AnyXMLEntry, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - container c { - anyxml data { - description "anyxml"; - } - } -}`, - }, - { - name: "test anydata", - wantNodeKind: "anydata", - wantEntryKind: AnyDataEntry, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - container c { - anydata data { - description "anydata"; - } - } -}`, - }, - } - for _, tt := range tests { - ms := NewModules() - if err := ms.Parse(tt.inModule, "test"); err != nil { - t.Errorf("%s: error parsing module 'test', got: %v, want: nil", tt.name, err) - } - - if errs := ms.Process(); len(errs) > 0 { - t.Errorf("%s: got module parsing errors", tt.name) - for i, err := range errs { - t.Errorf("%s: error #%d: %v", tt.name, i, err) - } - continue - } - - mod, ok := ms.Modules["test"] - if !ok { - t.Errorf("%s: did not find `test` module", tt.name) - continue - } - e := ToEntry(mod) - c := e.Dir["c"] - if c == nil { - t.Errorf("%s: did not find container c", tt.name) - continue - } - data := c.Dir["data"] - if data == nil { - t.Errorf("%s: did not find leaf c/data", tt.name) - continue - } - if got := data.Node.Kind(); got != tt.wantNodeKind { - t.Errorf("%s: want Node.Kind(): %q, got: %q", tt.name, tt.wantNodeKind, got) - } - if got := data.Kind; got != tt.wantEntryKind { - t.Errorf("%s: want Kind: %v, got: %v", tt.name, tt.wantEntryKind, got) - } - if got := data.Description; got != tt.wantNodeKind { - t.Errorf("%s: want data.Description: %q, got: %q", tt.name, tt.wantNodeKind, got) - } - } -} - -func getEntry(root *Entry, path []string) *Entry { - for _, elem := range path { - if root = root.Dir[elem]; root == nil { - break - } - } - return root -} - -func TestActionRPC(t *testing.T) { - tests := []struct { - name string - inModule string - operationPath []string - wantNodeKind string - wantError string - noInput bool - noOutput bool - }{ - { - name: "test action in container", - wantNodeKind: "action", - operationPath: []string{"c", "operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - container c { - action operation { - description "action"; - input { leaf string { type string; } } - output { leaf string { type string; } } - } - } -}`, - }, - - { - name: "test action in list", - wantNodeKind: "action", - operationPath: []string{"list", "operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - list list { - action operation { - description "action"; - input { leaf string { type string; } } - output { leaf string { type string; } } - } - } -}`, - }, - - { - name: "test action in container via grouping", - wantNodeKind: "action", - operationPath: []string{"c", "operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - grouping g { - action operation { - description "action"; - input { leaf string { type string; } } - output { leaf string { type string; } } - } - } - container c { uses g; } -}`, - }, - - { - name: "test action in list via grouping", - wantNodeKind: "action", - operationPath: []string{"list", "operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - grouping g { - action operation { - description "action"; - input { leaf string { type string; } } - output { leaf string { type string; } } - } - } - list list { uses g; } -}`, - }, - - { - name: "test rpc", - wantNodeKind: "rpc", - operationPath: []string{"operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - rpc operation { - description "rpc"; - input { - leaf string { type string; } - } - output { - leaf string { type string; } - } - } -}`, - }, - - { - name: "minimal rpc", - wantNodeKind: "rpc", - operationPath: []string{"operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - rpc operation { - description "rpc"; - } -}`, - noInput: true, - noOutput: true, - }, - - { - name: "input-only rpc", - wantNodeKind: "rpc", - operationPath: []string{"operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - rpc operation { - description "rpc"; - input { - leaf string { type string; } - } - } -}`, - noOutput: true, - }, - - { - name: "output-only rpc", - wantNodeKind: "rpc", - operationPath: []string{"operation"}, - inModule: `module test { - namespace "urn:test"; - prefix "test"; - rpc operation { - description "rpc"; - output { - leaf string { type string; } - } - } -}`, - noInput: true, - }, - - // test cases with errors (in module parsing) - { - name: "rpc not module child", - wantError: "test:6:5: unknown container field: rpc", - inModule: `module test { - namespace "urn:test"; - prefix "test"; - container c { - // error: "rpc" is not a valid sub-statement to "container" - rpc operation; - } -}`, - }, - - { - name: "action not valid leaf child", - wantError: "test:6:5: unknown leaf field: action", - inModule: `module test { - namespace "urn:test"; - prefix "test"; - leaf l { - // error: "operation" is not a valid sub-statement to "leaf" - action operation; - } -}`, - }, - - { - name: "action not valid leaf-list child", - wantError: "test:6:5: unknown leaf-list field: action", - inModule: `module test { - namespace "urn:test"; - prefix "test"; - leaf-list leaf-list { - // error: "operation" is not a valid sub-statement to "leaf-list" - action operation; - } -}`, - }, - } - for _, tt := range tests { - ms := NewModules() - if err := ms.Parse(tt.inModule, "test"); err != nil { - if got := err.Error(); got != tt.wantError { - t.Errorf("%s: error parsing module 'test', got error: %q, want: %q", tt.name, got, tt.wantError) - } - continue - } - - if errs := ms.Process(); len(errs) > 0 { - t.Errorf("%s: got %d module parsing errors", tt.name, len(errs)) - for i, err := range errs { - t.Errorf("%s: error #%d: %v", tt.name, i, err) - } - continue - } - - mod := ms.Modules["test"] - e := ToEntry(mod) - if e = getEntry(e, tt.operationPath); e == nil { - t.Errorf("%s: want child entry at: %v, got: nil", tt.name, tt.operationPath) - continue - } - if got := e.Node.Kind(); got != tt.wantNodeKind { - t.Errorf("%s: got `operation` node kind: %q, want: %q", tt.name, got, tt.wantNodeKind) - } else if got := e.Description; got != tt.wantNodeKind { - t.Errorf("%s: got `operation` Description: %q, want: %q", tt.name, got, tt.wantNodeKind) - } - // confirm the child RPCEntry was populated for the entry. - if e.RPC == nil { - t.Errorf("%s: entry at %v has nil RPC child, want: non-nil. Entry: %#v", tt.name, tt.operationPath, e) - } else if !tt.noInput && e.RPC.Input == nil { - t.Errorf("%s: RPCEntry has nil Input, want: non-nil. Entry: %#v", tt.name, e.RPC) - } else if !tt.noOutput && e.RPC.Output == nil { - t.Errorf("%s: RPCEntry has nil Output, want: non-nil. Entry: %#v", tt.name, e.RPC) - } - } -} - -var testIfFeatureModules = []struct { - name string - in string -}{ - { - name: "if-feature.yang", - in: `module if-feature { - namespace "urn:if-feature"; - prefix "feat"; - - feature ft-container; - feature ft-action; - feature ft-anydata1; - feature ft-anydata2; - feature ft-anyxml; - feature ft-choice; - feature ft-case; - feature ft-feature; - feature ft-leaf; - feature ft-bit; - feature ft-leaf-list; - feature ft-enum; - feature ft-list; - feature ft-notification; - feature ft-rpc; - feature ft-augment; - feature ft-identity; - feature ft-uses; - feature ft-refine; - feature ft-augment-uses; - - container cont { - if-feature ft-container; - action act { - if-feature ft-action; - } - } - - anydata data { - if-feature ft-anydata1; - if-feature ft-anydata2; - } - - anyxml xml { - if-feature ft-anyxml; - } - - choice ch { - if-feature ft-choice; - case cs { - if-feature ft-case; - } - } - - feature f { - if-feature ft-feature; - } - - leaf l { - if-feature ft-leaf; - type bits { - bit A { - if-feature ft-bit; - } - } - } - - leaf-list ll { - if-feature ft-leaf-list; - type enumeration { - enum zero { - if-feature ft-enum; - } - } - } - - list ls { - if-feature ft-list; - } - - notification n { - if-feature ft-notification; - } - - rpc r { - if-feature ft-rpc; - } - - augment "/cont" { - if-feature ft-augment; - uses g { - if-feature ft-augment-uses; - } - } - - identity id { - if-feature ft-identity; - } - - uses g { - if-feature ft-uses; - refine rf { - if-feature ft-refine; - } - } - - grouping g { - container gc {} - } -} -`, - }, -} - -func TestIfFeature(t *testing.T) { - entryIfFeatures := func(e *Entry) []*Value { - extra := e.Extra["if-feature"] - if len(extra) == 0 { - return nil - } - values := make([]*Value, len(extra)) - for i, ex := range extra { - values[i] = ex.(*Value) - } - return values - } - - featureByName := func(e *Entry, name string) *Feature { - for _, f := range e.Extra["feature"] { - ft := f.(*Feature) - if ft.Name == name { - return ft - } - } - return nil - } - - ms := NewModules() - for _, tt := range testIfFeatureModules { - if err := ms.Parse(tt.in, tt.name); err != nil { - t.Fatalf("could not parse module %s: %v", tt.name, err) - } - } - - if errs := ms.Process(); len(errs) > 0 { - t.Fatalf("could not process modules: %v", errs) - } - - mod, _ := ms.GetModule("if-feature") - - testcases := []struct { - name string - inIfFeatures []*Value - wantIfFeatures []string - }{ - // Node statements - { - name: "action", - inIfFeatures: entryIfFeatures(mod.Dir["cont"].Dir["act"]), - wantIfFeatures: []string{"ft-action"}, - }, - { - name: "anydata", - inIfFeatures: entryIfFeatures(mod.Dir["data"]), - wantIfFeatures: []string{"ft-anydata1", "ft-anydata2"}, - }, - { - name: "anyxml", - inIfFeatures: entryIfFeatures(mod.Dir["xml"]), - wantIfFeatures: []string{"ft-anyxml"}, - }, - { - name: "case", - inIfFeatures: entryIfFeatures(mod.Dir["ch"].Dir["cs"]), - wantIfFeatures: []string{"ft-case"}, - }, - { - name: "choice", - inIfFeatures: entryIfFeatures(mod.Dir["ch"]), - wantIfFeatures: []string{"ft-choice"}, - }, - { - name: "container", - inIfFeatures: entryIfFeatures(mod.Dir["cont"]), - wantIfFeatures: []string{"ft-container"}, - }, - { - name: "feature", - inIfFeatures: featureByName(mod, "f").IfFeature, - wantIfFeatures: []string{"ft-feature"}, - }, - { - name: "leaf", - inIfFeatures: entryIfFeatures(mod.Dir["l"]), - wantIfFeatures: []string{"ft-leaf"}, - }, - { - name: "leaf-list", - inIfFeatures: entryIfFeatures(mod.Dir["ll"]), - wantIfFeatures: []string{"ft-leaf-list"}, - }, - { - name: "list", - inIfFeatures: entryIfFeatures(mod.Dir["ls"]), - wantIfFeatures: []string{"ft-list"}, - }, - { - name: "notification", - inIfFeatures: entryIfFeatures(mod.Dir["n"]), - wantIfFeatures: []string{"ft-notification"}, - }, - { - name: "rpc", - inIfFeatures: entryIfFeatures(mod.Dir["r"]), - wantIfFeatures: []string{"ft-rpc"}, - }, - // Other statements - { - name: "augment", - inIfFeatures: entryIfFeatures(mod.Dir["cont"].Augmented[0]), - wantIfFeatures: []string{"ft-augment"}, - }, - { - name: "bit", - inIfFeatures: mod.Dir["l"].Node.(*Leaf).Type.Bit[0].IfFeature, - wantIfFeatures: []string{"ft-bit"}, - }, - { - name: "enum", - inIfFeatures: mod.Dir["ll"].Node.(*Leaf).Type.Enum[0].IfFeature, - wantIfFeatures: []string{"ft-enum"}, - }, - { - name: "identity", - inIfFeatures: mod.Identities[0].IfFeature, - wantIfFeatures: []string{"ft-identity"}, - }, - { - name: "refine", - inIfFeatures: ms.Modules["if-feature"].Uses[0].Refine[0].IfFeature, - wantIfFeatures: []string{"ft-refine"}, - }, - { - name: "uses", - inIfFeatures: ms.Modules["if-feature"].Uses[0].IfFeature, - wantIfFeatures: []string{"ft-uses"}, - }, - { - // Verify that if-feature field defined in "uses" is correctly propagated to container - name: "uses", - inIfFeatures: entryIfFeatures(mod.Dir["gc"]), - wantIfFeatures: []string{"ft-uses"}, - }, - { - // Verify that if-feature field defined in "augment" and in "augment > uses" is correctly propagated to container - name: "augment-uses", - inIfFeatures: entryIfFeatures(mod.Dir["cont"].Dir["gc"]), - wantIfFeatures: []string{"ft-augment-uses", "ft-augment"}, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - var names []string - for _, f := range tc.inIfFeatures { - names = append(names, f.Name) - } - - if !reflect.DeepEqual(names, tc.wantIfFeatures) { - t.Errorf("%s: did not get expected if-features, got %v, want %v", tc.name, names, tc.wantIfFeatures) - } - }) - } -} - -var testNotificationModules = []struct { - name string - in string -}{ - { - name: "notification.yang", - in: `module notification { - namespace "urn:notification"; - prefix "n"; - - notification n {} - - grouping g { - notification g-n {} - } - - container cont { - notification cont-n {} - } - - list ls { - notification ls-n {} - uses g; - } - - augment "/cont" { - notification aug-n {} - } -} -`, - }, -} - -func TestNotification(t *testing.T) { - ms := NewModules() - for _, tt := range testNotificationModules { - if err := ms.Parse(tt.in, tt.name); err != nil { - t.Fatalf("could not parse module %s: %v", tt.name, err) - } - } - - if errs := ms.Process(); len(errs) > 0 { - t.Fatalf("could not process modules: %v", errs) - } - - mod, _ := ms.GetModule("notification") - - testcases := []struct { - name string - wantPath []string - }{ - { - name: "module", - wantPath: []string{"n"}, - }, - { - name: "container", - wantPath: []string{"cont", "cont-n"}, - }, - { - name: "list", - wantPath: []string{"ls", "ls-n"}, - }, - { - name: "grouping", - wantPath: []string{"ls", "g-n"}, - }, - { - name: "augment", - wantPath: []string{"cont", "aug-n"}, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - if e := getEntry(mod, tc.wantPath); e == nil || e.Node.Kind() != "notification" { - t.Errorf("%s: want notification entry at: %v, got: %+v", tc.name, tc.wantPath, e) - } - }) - } -} - -// addTreeE takes an input Entry and appends it to a directory, keyed by path, to the Entry. -// If the Entry has children, they are appended to the directory recursively. Used in test -// cases where a path is to be referred to. -func addTreeE(e *Entry, dir map[string]*Entry) { - for _, ch := range e.Dir { - dir[ch.Path()] = ch - if ch.Dir != nil { - addTreeE(ch, dir) - } - } -} - -func TestEntryFind(t *testing.T) { - tests := []struct { - name string - inModules map[string]string - inBaseEntryPath string - wantEntryPath map[string]string // keyed on path to find, with path expected as value. - wantError string - }{{ - name: "intra module find", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - leaf a { type string; } - leaf b { type string; } - - container c { leaf d { type string; } } - - rpc rpc1 { - input { leaf input1 { type string; } } - } - - container e { - action operation { - description "action"; - input { leaf input1 { type string; } } - output { leaf output1 { type string; } } - } - } - - } - `, - }, - inBaseEntryPath: "/test/a", - wantEntryPath: map[string]string{ - // Absolute path with no prefixes. - "/b": "/test/b", - // Relative path with no prefixes. - "../b": "/test/b", - // Absolute path with prefixes. - "/t:b": "/test/b", - // Relative path with prefixes. - "../t:b": "/test/b", - // Find within a directory. - "/c/d": "/test/c/d", - // Find within a directory specified relatively. - "../c/d": "/test/c/d", - // Find within a relative directory with prefixes. - "../t:c/t:d": "/test/c/d", - "../t:c/d": "/test/c/d", - "../c/t:d": "/test/c/d", - // Find within an absolute directory with prefixes. - "/t:c/d": "/test/c/d", - "/c/t:d": "/test/c/d", - "../t:rpc1/input": "/test/rpc1/input", - "/t:rpc1/input": "/test/rpc1/input", - "/t:rpc1/t:input": "/test/rpc1/input", - "/t:e/operation/input": "/test/e/operation/input", - "/t:e/operation/output": "/test/e/operation/output", - "/t:e/t:operation/t:input": "/test/e/operation/input", - "/t:e/t:operation/t:output": "/test/e/operation/output", - }, - }, { - name: "submodule find", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - include test1; - - leaf a { type string; } - leaf b { type string; } - - container c { leaf d { type string; } } - - rpc rpc1 { - input { leaf input1 { type string; } } - } - - container e { - action operation { - description "action"; - input { leaf input1 { type string; } } - output { leaf output1 { type string; } } - } - } - - } - `, - "test1.yang": ` - submodule test1 { - belongs-to test { - prefix "t"; - } - - leaf d { type string; } - } - `, - }, - inBaseEntryPath: "/test/d", - wantEntryPath: map[string]string{ - // Absolute path with no prefixes. - "/b": "/test/b", - // Relative path with no prefixes. - "../b": "/test/b", - // Absolute path with prefixes. - "/t:b": "/test/b", - // Relative path with prefixes. - "../t:b": "/test/b", - // Find within a directory. - "/c/d": "/test/c/d", - // Find within a directory specified relatively. - "../c/d": "/test/c/d", - // Find within a relative directory with prefixes. - "../t:c/t:d": "/test/c/d", - "../t:c/d": "/test/c/d", - "../c/t:d": "/test/c/d", - // Find within an absolute directory with prefixes. - "/t:c/d": "/test/c/d", - "/c/t:d": "/test/c/d", - "../t:rpc1/input": "/test/rpc1/input", - "/t:rpc1/input": "/test/rpc1/input", - "/t:rpc1/t:input": "/test/rpc1/input", - "/t:e/operation/input": "/test/e/operation/input", - "/t:e/operation/output": "/test/e/operation/output", - "/t:e/t:operation/t:input": "/test/e/operation/input", - "/t:e/t:operation/t:output": "/test/e/operation/output", - }, - }, { - name: "inter-module find", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - import foo { prefix foo; } - import bar { prefix baz; } - - leaf ctx { type string; } - leaf other { type string; } - leaf conflict { type string; } - }`, - "foo.yang": ` - module foo { - prefix "foo"; // matches the import above - namespace "urn:foo"; - - container bar { - leaf baz { type string; } - } - - leaf conflict { type string; } - }`, - "bar.yang": ` - module bar { - prefix "bar"; // does not match import in test - namespace "urn:b"; - - container fish { - leaf chips { type string; } - } - - leaf conflict { type string; } - }`, - }, - inBaseEntryPath: "/test/ctx", - wantEntryPath: map[string]string{ - // Check we can still do intra module lookups - "../other": "/test/other", - "/other": "/test/other", - "/foo:bar/foo:baz": "/foo/bar/baz", - // Technically partially prefixed paths to remote modules are - // not legal - check whether we can resolve them. - "/foo:bar/baz": "/foo/bar/baz", - // With mismatched prefixes. - "/baz:fish/baz:chips": "/bar/fish/chips", - // With conflicting node names - "/conflict": "/test/conflict", - "/foo:conflict": "/foo/conflict", - "/baz:conflict": "/bar/conflict", - "/t:conflict": "/test/conflict", - }, - }} - - for _, tt := range tests { - ms := NewModules() - var errs []error - for n, m := range tt.inModules { - if err := ms.Parse(m, n); err != nil { - errs = append(errs, err) - } - } - - if len(errs) > 0 { - t.Errorf("%s: ms.Parse(), got unexpected error parsing input modules: %v", tt.name, errs) - continue - } - - if errs := ms.Process(); len(errs) > 0 { - t.Errorf("%s: ms.Process(), got unexpected error processing entries: %v", tt.name, errs) - continue - } - - dir := map[string]*Entry{} - for _, m := range ms.Modules { - addTreeE(ToEntry(m), dir) - } - - if _, ok := dir[tt.inBaseEntryPath]; !ok { - t.Errorf("%s: could not find entry %s within the dir: %v", tt.name, tt.inBaseEntryPath, dir) - } - - for path, want := range tt.wantEntryPath { - got := dir[tt.inBaseEntryPath].Find(path) - if got.Path() != want { - t.Errorf("%s: (entry %s).Find(%s), did not find path, got: %v, want: %v, errors: %v", tt.name, dir[tt.inBaseEntryPath].Path(), path, got.Path(), want, dir[tt.inBaseEntryPath].Errors) - } - } - } -} - -func TestEntryTypes(t *testing.T) { - leafSchema := &Entry{Name: "leaf-schema", Kind: LeafEntry, Type: &YangType{Kind: Ystring}} - - containerSchema := &Entry{ - Name: "container-schema", - Kind: DirectoryEntry, - Dir: map[string]*Entry{ - "config": { - Dir: map[string]*Entry{ - "leaf1": { - Kind: LeafEntry, - Name: "Leaf1Name", - Type: &YangType{Kind: Ystring}, - }, - }, - }, - }, - } - - emptyContainerSchema := &Entry{ - Name: "empty-container-schema", - Kind: DirectoryEntry, - } - - leafListSchema := &Entry{ - Kind: LeafEntry, - ListAttr: &ListAttr{MinElements: 0}, - Type: &YangType{Kind: Ystring}, - Name: "leaf-list-schema", - } - - listSchema := &Entry{ - Name: "list-schema", - Kind: DirectoryEntry, - ListAttr: &ListAttr{MinElements: 0}, - Dir: map[string]*Entry{ - "leaf-name": { - Kind: LeafEntry, - Name: "LeafName", - Type: &YangType{Kind: Ystring}, - }, - }, - } - - choiceSchema := &Entry{ - Kind: ChoiceEntry, - Name: "Choice1Name", - Dir: map[string]*Entry{ - "case1": { - Kind: CaseEntry, - Name: "case1", - Dir: map[string]*Entry{ - "case1-leaf1": { - Kind: LeafEntry, - Name: "Case1Leaf1", - Type: &YangType{Kind: Ystring}, - }, - }, - }, - }, - } - - type SchemaType string - const ( - Leaf SchemaType = "Leaf" - Container SchemaType = "Container" - LeafList SchemaType = "LeafList" - List SchemaType = "List" - Choice SchemaType = "Choice" - Case SchemaType = "Case" - ) - - tests := []struct { - desc string - schema *Entry - wantType SchemaType - }{ - { - desc: "leaf", - schema: leafSchema, - wantType: Leaf, - }, - { - desc: "container", - schema: containerSchema, - wantType: Container, - }, - { - desc: "empty container", - schema: emptyContainerSchema, - wantType: Container, - }, - { - desc: "leaf-list", - schema: leafListSchema, - wantType: LeafList, - }, - { - desc: "list", - schema: listSchema, - wantType: List, - }, - { - desc: "choice", - schema: choiceSchema, - wantType: Choice, - }, - { - desc: "case", - schema: choiceSchema.Dir["case1"], - wantType: Case, - }, - } - - for _, tt := range tests { - gotm := map[SchemaType]bool{ - Leaf: tt.schema.IsLeaf(), - Container: tt.schema.IsContainer(), - LeafList: tt.schema.IsLeafList(), - List: tt.schema.IsList(), - Choice: tt.schema.IsChoice(), - Case: tt.schema.IsCase(), - } - - for stype, got := range gotm { - if want := (stype == tt.wantType); got != want { - t.Errorf("%s: got Is%v? %t, want Is%v? %t", tt.desc, stype, got, stype, want) - } - } - } -} - -func TestFixChoice(t *testing.T) { - choiceEntry := &Entry{ - Name: "choiceEntry", - Kind: ChoiceEntry, - Dir: map[string]*Entry{ - "unnamedAnyDataCase": { - Name: "unnamedAnyDataCase", - Kind: AnyDataEntry, - Node: &AnyData{ - Parent: &Container{ - Name: "AnyDataParentNode", - }, - Name: "unnamedAnyDataCase", - Source: &Statement{ - Keyword: "anyData-keyword", - HasArgument: true, - Argument: "anyData-argument", - statements: nil, - }, - Extensions: []*Statement{ - { - Keyword: "anyData-extension", - HasArgument: true, - Argument: "anyData-extension-arg", - statements: nil, - }, - }, - }, - }, - "unnamedAnyXMLCase": { - Name: "unnamedAnyXMLCase", - Kind: AnyXMLEntry, - Node: &AnyXML{ - Parent: &Container{ - Name: "AnyXMLParentNode", - }, - Name: "unnamedAnyXMLCase", - Source: &Statement{ - Keyword: "anyXML-keyword", - HasArgument: true, - Argument: "anyXML-argument", - statements: nil, - }, - Extensions: []*Statement{ - { - Keyword: "anyXML-extension", - HasArgument: true, - Argument: "anyXML-extension-arg", - statements: nil, - }, - }, - }, - }, - "unnamedContainerCase": { - Name: "unnamedContainerCase", - Kind: DirectoryEntry, - Node: &Container{ - Parent: &Container{ - Name: "AnyContainerNode", - }, - Name: "unnamedContainerCase", - Source: &Statement{ - Keyword: "container-keyword", - HasArgument: true, - Argument: "container-argument", - statements: nil, - }, - Extensions: []*Statement{ - { - Keyword: "container-extension", - HasArgument: true, - Argument: "container-extension-arg", - statements: nil, - }, - }, - }, - }, - "unnamedLeafCase": { - Name: "unnamedLeafCase", - Kind: LeafEntry, - Node: &Leaf{ - Parent: &Container{ - Name: "leafParentNode", - }, - Name: "unnamedLeafCase", - Source: &Statement{ - Keyword: "leaf-keyword", - HasArgument: true, - Argument: "leaf-argument", - statements: nil, - }, - Extensions: []*Statement{ - { - Keyword: "leaf-extension", - HasArgument: true, - Argument: "leaf-extension-arg", - statements: nil, - }, - }, - }, - }, - "unnamedLeaf-ListCase": { - Name: "unnamedLeaf-ListCase", - Kind: LeafEntry, - Node: &LeafList{ - Parent: &Container{ - Name: "LeafListNode", - }, - Name: "unnamedLeaf-ListCase", - Source: &Statement{ - Keyword: "leaflist-keyword", - HasArgument: true, - Argument: "leaflist-argument", - statements: nil, - }, - Extensions: []*Statement{ - { - Keyword: "leaflist-extension", - HasArgument: true, - Argument: "leaflist-extension-arg", - statements: nil, - }, - }, - }, - }, - "unnamedListCase": { - Name: "unnamedListCase", - Kind: DirectoryEntry, - Node: &List{ - Parent: &Container{ - Name: "ListNode", - }, - Name: "unnamedListCase", - Source: &Statement{ - Keyword: "list-keyword", - HasArgument: true, - Argument: "list-argument", - statements: nil, - }, - Extensions: []*Statement{ - { - Keyword: "list-extension", - HasArgument: true, - Argument: "list-extension-arg", - statements: nil, - }, - }, - }, - }, - }, - } - - choiceEntry.FixChoice() - - for _, e := range []string{"AnyData", "AnyXML", "Container", - "Leaf", "Leaf-List", "List"} { - entryName := "unnamed" + e + "Case" - t.Run(entryName, func(t *testing.T) { - - insertedCase := choiceEntry.Dir[entryName] - originalCase := insertedCase.Dir[entryName] - - insertedNode := insertedCase.Node - if insertedNode.Kind() != "case" { - t.Errorf("Got inserted node type %s, expected case", - insertedNode.Kind()) - } - - originalNode := originalCase.Node - if originalNode.Kind() != strings.ToLower(e) { - t.Errorf("Got original node type %s, expected %s", - originalNode.Kind(), strings.ToLower(e)) - } - - if insertedNode.ParentNode() != originalNode.ParentNode() { - t.Errorf("Got inserted node's parent node %v, expected %v", - insertedNode.ParentNode(), originalNode.ParentNode()) - } - - if insertedNode.NName() != originalNode.NName() { - t.Errorf("Got inserted node's name %s, expected %s", - insertedNode.NName(), originalNode.NName()) - } - - if insertedNode.Statement() != originalNode.Statement() { - t.Errorf("Got inserted node's statement %v, expected %v", - insertedNode.Statement(), originalNode.Statement()) - } - - if len(insertedNode.Exts()) != len(originalNode.Exts()) { - t.Errorf("Got inserted node extensions slice len %d, expected %v", - len(insertedNode.Exts()), len(originalNode.Exts())) - } - - for i, e := range insertedNode.Exts() { - if e != originalNode.Exts()[i] { - t.Errorf("Got inserted node's extension %v at index %d, expected %v", - e, i, originalNode.Exts()[i]) - } - } - }) - } -} - -func mustReadFile(path string) string { - s, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - return string(s) -} - -func TestDeviation(t *testing.T) { - type deviationTest struct { - path string - entry *Entry // entry is the entry that is wanted at a particular path, if a field is left as nil, it is not checked. - } - tests := []struct { - desc string - inFiles map[string]string - inParseOptions Options - wants map[string][]deviationTest - wantParseErrSubstring string - wantProcessErrSubstring string - }{{ - desc: "deviation with add", - inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate.yang"))}, - wants: map[string][]deviationTest{ - "deviate": {{ - path: "/target/add/config", - entry: &Entry{ - Config: TSFalse, - }, - }, { - path: "/target/add/default", - entry: &Entry{ - Default: []string{"a default value"}, - }, - }, { - path: "/target/add/default-typedef", - entry: &Entry{ - Default: nil, - }, - }, { - path: "/target/add/default-list", - entry: &Entry{ - Default: []string{"foo", "bar", "foo"}, - }, - }, { - path: "/target/add/default-list-typedef-default", - entry: &Entry{ - Default: nil, - }, - }, { - path: "/target/add/mandatory", - entry: &Entry{ - Mandatory: TSTrue, - }, - }, { - path: "/target/add/min-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MinElements: 42, - }, - deviatePresence: deviationPresence{ - hasMinElements: true, - }, - }, - }, { - path: "/target/add/max-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MaxElements: 42, - }, - deviatePresence: deviationPresence{ - hasMaxElements: true, - }, - }, - }, { - path: "/target/add/max-and-min-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MinElements: 42, - MaxElements: 42, - }, - deviatePresence: deviationPresence{ - hasMinElements: true, - hasMaxElements: true, - }, - }, - }, { - path: "/target/add/units", - entry: &Entry{ - Units: "fish per second", - }, - }}, - }, - }, { - desc: "error case - deviation add that already has a default", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { - type string; - default "fish"; - } - - deviation /a { - deviate add { - default "fishsticks"; - } - } - }`, - }, - wantProcessErrSubstring: "already has a default value", - }, { - desc: "error case - deviate type not recognized", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { type string; } - - deviation /a { - deviate shrink { - max-elements 42; - } - } - }`, - }, - wantProcessErrSubstring: "unknown deviation type", - }, { - desc: "error case - deviation add max-element to non-list", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { type string; } - - deviation /a { - deviate add { - max-elements 42; - } - } - }`, - }, - wantProcessErrSubstring: "tried to deviate max-elements on a non-list type", - }, { - desc: "error case - deviation add min elements to non-list", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { type string; } - - deviation /a { - deviate add { - min-elements 42; - } - } - }`, - }, - wantProcessErrSubstring: "tried to deviate min-elements on a non-list type", - }, { - desc: "error case - deviation delete max-element on non-list", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { type string; } - - deviation /a { - deviate delete { - max-elements 42; - } - } - }`, - }, - wantProcessErrSubstring: "tried to deviate max-elements on a non-list type", - }, { - desc: "error case - deviation delete min elements on non-list", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { type string; } - - deviation /a { - deviate delete { - min-elements 42; - } - } - }`, - }, - wantProcessErrSubstring: "tried to deviate min-elements on a non-list type", - }, { - desc: "deviation - not supported", - inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-notsupported.yang"))}, - wants: map[string][]deviationTest{ - "deviate": {{ - path: "/target", - }, { - path: "/target-list", - }, { - path: "/a-leaf", - }, { - path: "/a-leaflist", - }, { - path: "survivor", - entry: &Entry{Name: "survivor"}, - }}, - }, - }, { - desc: "deviation - not supported but ignored by option", - inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-notsupported.yang"))}, - inParseOptions: Options{ - DeviateOptions: DeviateOptions{ - IgnoreDeviateNotSupported: true, - }, - }, - wants: map[string][]deviationTest{ - "deviate": {{ - path: "/target", - entry: &Entry{Name: "target"}, - }, { - path: "/target-list", - entry: &Entry{Name: "target-list"}, - }, { - path: "/a-leaf", - entry: &Entry{Name: "a-leaf"}, - }, { - path: "/a-leaflist", - entry: &Entry{Name: "a-leaflist"}, - }, { - path: "survivor", - entry: &Entry{Name: "survivor"}, - }}, - }, - }, { - desc: "deviation removing non-existent node", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - deviation /a/b/c { - deviate not-supported; - } - } - `, - }, - wantProcessErrSubstring: "cannot find target node to deviate", - }, { - desc: "deviation not supported across modules", - inFiles: map[string]string{ - "source": ` - module source { - prefix "s"; - namespace "urn:s"; - - leaf a { type string; } - leaf b { type string; } - }`, - "deviation": ` - module deviation { - prefix "d"; - namespace "urn:d"; - - import source { prefix s; } - - deviation /s:a { - deviate not-supported; - } - }`, - }, - wants: map[string][]deviationTest{ - "source": {{ - path: "/a", - }, { - path: "/b", - entry: &Entry{}, - }}, - }, - }, { - desc: "deviation with replace", - inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-replace.yang"))}, - wants: map[string][]deviationTest{ - "deviate": {{ - path: "/target/replace/config", - entry: &Entry{ - Config: TSFalse, - }, - }, { - path: "/target/replace/default", - entry: &Entry{ - Default: []string{"a default value"}, - }, - }, { - path: "/target/replace/default-list", - entry: &Entry{ - Default: []string{"nematodes"}, - }, - }, { - path: "/target/replace/mandatory", - entry: &Entry{ - Mandatory: TSTrue, - }, - }, { - path: "/target/replace/min-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MinElements: 42, - }, - deviatePresence: deviationPresence{ - hasMinElements: true, - }, - }, - }, { - path: "/target/replace/max-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MaxElements: 42, - }, - deviatePresence: deviationPresence{ - hasMaxElements: true, - }, - }, - }, { - path: "/target/replace/max-and-min-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MinElements: 42, - MaxElements: 42, - }, - deviatePresence: deviationPresence{ - hasMinElements: true, - hasMaxElements: true, - }, - }, - }, { - path: "/target/replace/units", - entry: &Entry{ - Units: "fish per second", - }, - }, { - path: "/target/replace/type", - entry: &Entry{ - Type: &YangType{ - Name: "uint16", - Kind: Yuint16, - }, - }, - }}, - }, - }, { - desc: "deviation with delete", - inFiles: map[string]string{"deviate": mustReadFile(filepath.Join("testdata", "deviate-delete.yang"))}, - wants: map[string][]deviationTest{ - "deviate": {{ - path: "/target/delete/config", - entry: &Entry{ - Config: TSUnset, - }, - }, { - path: "/target/delete/default", - entry: &Entry{}, - }, { - path: "/target/delete/mandatory", - entry: &Entry{ - Mandatory: TSUnset, - }, - }, { - path: "/target/delete/min-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MinElements: 0, - }, - deviatePresence: deviationPresence{ - hasMinElements: true, - }, - }, - }, { - path: "/target/delete/max-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MaxElements: math.MaxUint64, - }, - deviatePresence: deviationPresence{ - hasMaxElements: true, - }, - }, - }, { - path: "/target/delete/max-and-min-elements", - entry: &Entry{ - ListAttr: &ListAttr{ - MinElements: 0, - MaxElements: math.MaxUint64, - }, - deviatePresence: deviationPresence{ - hasMinElements: true, - hasMaxElements: true, - }, - }, - }, { - path: "/target/delete/units", - entry: &Entry{ - Units: "", - }, - }}, - }, - }, { - // TODO(wenovus): Support deviate delete for leaf-lists for config-false leafs once its semantics are clear. - // https://github.com/mbj4668/pyang/issues/756 - desc: "error case - deviation delete on a leaf-list", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf-list a { - type string; - default "fish"; - } - - deviation /a { - deviate delete { - default "fishsticks"; - } - } - }`, - }, - wantProcessErrSubstring: "deviate delete on default statements unsupported for leaf-lists", - }, { - desc: "error case - deviation delete of default has different keyword value", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { - type string; - default "fish"; - } - - deviation /a { - deviate delete { - default "fishsticks"; - } - } - }`, - }, - wantProcessErrSubstring: "non-matching keyword", - }, { - desc: "error case - deviation delete where the default didn't exist", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf a { - type string; - } - - deviation /a { - deviate delete { - default "fishsticks"; - } - } - }`, - }, - wantProcessErrSubstring: "default statement that doesn't exist", - }, { - desc: "error case - deviation delete of min-elements has different keyword value", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf-list a { type string; } - - deviation /a { - deviate delete { - min-elements 42; - } - } - }`, - }, - wantProcessErrSubstring: "differs from deviation's min-element value", - }, { - desc: "error case - deviation delete of max-elements has different keyword value", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - leaf-list a { - type string; - max-elements 100; - } - - deviation /a { - deviate delete { - max-elements 42; - } - } - }`, - }, - wantProcessErrSubstring: "differs from deviation's max-element value", - }, { - desc: "deviation using locally defined typedef", - inFiles: map[string]string{ - "deviate": ` - module deviate { - prefix "d"; - namespace "urn:d"; - - import source { prefix s; } - - typedef rstr { - type string { - pattern "a.*"; - } - } - - deviation /s:a { - deviate replace { - type rstr; - } - } - } - `, - "source": ` - module source { - prefix "s"; - namespace "urn:s"; - - leaf a { type uint16; } - } - `, - }, - wants: map[string][]deviationTest{ - "source": {{ - path: "/a", - entry: &Entry{ - Type: &YangType{ - Name: "rstr", - Kind: Ystring, - Pattern: []string{"a.*"}, - }, - }, - }}, - }, - }, { - desc: "complex deviation of multiple leaves", - inFiles: map[string]string{ - "foo": ` - module foo { - prefix "f"; - namespace "urn:f"; - - container a { leaf b { type string; } } - - typedef abc { type boolean; } - typedef abt { type uint32; } - - deviation /a/b { - // typedef is not valid here. - //typedef abc { - // type boolean; - //} - deviate replace { type abc; } - } - - deviation /a/b { - // typedef is not valid here. - //typedef abt { - // type uint16; - //} - deviate replace { type abt; } - } - }`, - }, - wants: map[string][]deviationTest{ - "foo": {{ - path: "/a/b", - entry: &Entry{ - Type: &YangType{ - Name: "abt", - Kind: Yuint32, - }, - }, - }}, - }, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - ms.ParseOptions = tt.inParseOptions - - for name, mod := range tt.inFiles { - if err := ms.Parse(mod, name); err != nil { - if diff := errdiff.Substring(err, tt.wantParseErrSubstring); diff != "" { - t.Fatalf("error parsing module %s, %s", name, diff) - } - } - } - - errs := ms.Process() - if len(errs) == 0 { - // Add a nil error to compare against the wanted error string. - errs = append(errs, nil) - } - var match bool - for _, err := range errs { - if diff := errdiff.Substring(err, tt.wantProcessErrSubstring); diff == "" { - match = true - break - } - } - if !match { - t.Fatalf("got errs: %v, want: %v", errs, tt.wantProcessErrSubstring) - } - - if tt.wantProcessErrSubstring == "" && len(tt.wants) == 0 { - t.Fatalf("test case expects no error and no entry. Please change your test case to contain one of them.") - } - - for mod, tcs := range tt.wants { - m, errs := ms.GetModule(mod) - if errs != nil { - t.Errorf("couldn't find module %s", mod) - continue - } - - for idx, want := range tcs { - got := m.Find(want.path) - switch { - case got == nil && want.entry != nil: - t.Errorf("%d: expected entry %s does not exist", idx, want.path) - continue - case got != nil && want.entry == nil: - t.Errorf("%d: unexpected entry %s exists, got: %v", idx, want.path, got) - continue - case want.entry == nil: - continue - } - - if got.Config != want.entry.Config { - t.Errorf("%d (%s): did not get expected config statement, got: %v, want: %v", idx, want.path, got.Config, want.entry.Config) - } - - if diff := cmp.Diff(got.Default, want.entry.Default, cmpopts.EquateEmpty()); diff != "" { - t.Errorf("%d (%s): did not get expected default statement, (-got, +want): %s", idx, want.path, diff) - } - - if got.Mandatory != want.entry.Mandatory { - t.Errorf("%d (%s): did not get expected mandatory statement, got: %v, want: %v", idx, want.path, got.Mandatory, want.entry.Mandatory) - } - - if want.entry.ListAttr != nil { - if got.ListAttr == nil { - t.Errorf("%d (%s): listattr was nil for an entry expected to be a list at %s", idx, want.path, want.path) - continue - } - if want.entry.deviatePresence.hasMinElements { - if gotMin, wantMin := got.ListAttr.MinElements, want.entry.ListAttr.MinElements; gotMin != wantMin { - t.Errorf("%d (%s): min-elements, got: %v, want: %v", idx, want.path, gotMin, wantMin) - } - } - if want.entry.deviatePresence.hasMaxElements { - if gotMax, wantMax := got.ListAttr.MaxElements, want.entry.ListAttr.MaxElements; gotMax != wantMax { - t.Errorf("%d (%s): max-elements, got: %v, want: %v", idx, want.path, gotMax, wantMax) - } - } - } - - if want.entry.Type != nil { - if got.Type.Name != want.entry.Type.Name { - t.Errorf("%d (%s): type name, got: %s, want: %s", idx, want.path, got.Type.Name, want.entry.Type.Name) - } - - if got.Type.Kind != want.entry.Type.Kind { - t.Errorf("%d (%s): type kind, got: %s, want: %s", idx, want.path, got.Type.Kind, want.entry.Type.Kind) - } - } - - if got.Units != want.entry.Units { - t.Errorf("%d (%s): did not get expected units statement, got: %s, want: %s", idx, want.path, got.Units, want.entry.Units) - } - } - } - }) - } -} - -func TestLeafEntry(t *testing.T) { - tests := []struct { - name string - inModules map[string]string - wantEntryPath string - wantEntryCustomTest func(t *testing.T, e *Entry) - wantErrSubstr string - }{{ - name: "direct decimal64 type", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - leaf "gain-adjustment" { - type "decimal64" { - fraction-digits "1"; - range "-12.0..12.0"; - } - default "0.0"; - } - } - `, - }, - wantEntryPath: "/test/gain-adjustment", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.Type.FractionDigits, 1; got != want { - t.Errorf("got %d, want %d", got, want) - } - if got, want := e.Mandatory, TSUnset; got != want { - t.Errorf("got %d, want %d", got, want) - } - if got, want := e.Type.Range, (YangRange{Rf(-120, 120, 1)}); !cmp.Equal(got, want) { - t.Errorf("Range got: %v, want: %v", got, want) - } - }, - }, { - name: "typedef decimal64 type", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - typedef "optical-dB" { - type "decimal64" { - fraction-digits "1"; - } - } - - leaf "gain-adjustment" { - type "optical-dB" { - range "-12.0..12.0"; - } - default "0.0"; - } - } - `, - }, - wantEntryPath: "/test/gain-adjustment", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.Type.FractionDigits, 1; got != want { - t.Errorf("got %d, want %d", got, want) - } - if diff := cmp.Diff(e.Type.Range, YangRange{Rf(-120, 120, 1)}); diff != "" { - t.Errorf("Range (-got, +want):\n%s", diff) - } - }, - }, { - name: "typedef decimal64 type with overriding fraction-digits", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - typedef "optical-dB" { - type "decimal64" { - fraction-digits "1"; - } - } - - leaf "gain-adjustment" { - type "optical-dB" { - fraction-digits "2"; - range "-12.0..12.0"; - } - default "0.0"; - } - } - `, - }, - wantErrSubstr: "overriding of fraction-digits not allowed", - }, { - name: "leaf mandatory true", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - leaf "mandatory" { - type "string" { - } - mandatory true; - } - } - `, - }, - wantEntryPath: "/test/mandatory", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.Mandatory, TSTrue; got != want { - t.Errorf("got %d, want %d", got, want) - } - }, - }, { - name: "leaf mandatory false", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - leaf "mandatory" { - type "string" { - } - mandatory false; - } - } - `, - }, - wantEntryPath: "/test/mandatory", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.Mandatory, TSFalse; got != want { - t.Errorf("got %d, want %d", got, want) - } - }, - }, { - name: "leaf description", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - leaf "mandatory" { - type "string" { - } - description "I am a leaf"; - } - } - `, - }, - wantEntryPath: "/test/mandatory", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.Description, "I am a leaf"; got != want { - t.Errorf("got %q, want %q", got, want) - } - }, - }} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ms := NewModules() - var errs []error - for n, m := range tt.inModules { - if err := ms.Parse(m, n); err != nil { - errs = append(errs, err) - } - } - - if len(errs) > 0 { - t.Fatalf("ms.Parse(), got unexpected error parsing input modules: %v", errs) - } - - if errs := ms.Process(); len(errs) > 0 { - if len(errs) == 1 { - if diff := errdiff.Substring(errs[0], tt.wantErrSubstr); diff != "" { - t.Fatalf("did not get expected error, %s", diff) - } - return - } - t.Fatalf("ms.Process(), got too many errors processing entries: %v", errs) - } - - dir := map[string]*Entry{} - for _, m := range ms.Modules { - addTreeE(ToEntry(m), dir) - } - - e, ok := dir[tt.wantEntryPath] - if !ok { - t.Fatalf("could not find entry %s within the dir: %v", tt.wantEntryPath, dir) - } - tt.wantEntryCustomTest(t, e) - }) - } -} - -func TestLess(t *testing.T) { - sErrors := sortedErrors{ - {"testfile0", errors.New("test error0")}, - {"testfile1", errors.New("test error1")}, - {"testfile1:1", errors.New("test error2")}, - {"testfile2:1", errors.New("test error3")}, - {"testfile2:1:1", errors.New("test error4")}, - {"testfile3:1:1:error5", errors.New("test error5")}, - {"testfile3:1:2:error6", errors.New("test error6")}, - {"testfile3:1:1:error7", errors.New("test error7")}, - } - - tests := []struct { - desc string - i int - j int - want bool - }{{ - desc: "compare two different strings without seperator ':'", - i: 0, - j: 1, - want: true, - }, { - desc: "compare two different strings without seperator ':'", - i: 1, - j: 0, - want: false, - }, { - desc: "compare one slice in a string with two slices in another string", - i: 1, - j: 2, - want: true, - }, { - desc: "compare two different strings with two slices each", - i: 2, - j: 3, - want: true, - }, { - desc: "compare two different strings with two slices each", - i: 3, - j: 2, - want: false, - }, { - desc: "compare two slices in a string with three slices in another string", - i: 3, - j: 4, - want: true, - }, { - desc: "compare three slices in a string with two slices in another string", - i: 4, - j: 3, - want: false, - }, { - desc: "compare two different strings with four slices each", - i: 5, - j: 6, - want: true, - }, { - desc: "compare two different strings with four slices each", - i: 6, - j: 5, - want: false, - }, { - desc: "compare two identical strings without separator ':'", - i: 1, - j: 1, - want: false, - }, { - desc: "compare two identical strings with two slices", - i: 2, - j: 2, - want: false, - }, { - desc: "compare two identical strings with three slices", - i: 4, - j: 4, - want: false, - }, { - desc: "compare two identical strings with four slices", - i: 5, - j: 5, - want: false, - }, { - desc: "compare different strings with four slices", - i: 7, - j: 5, - want: false, - }, { - desc: "compare different strings with four slices", - i: 5, - j: 7, - want: true, - }} - var cmpSymbol byte - for _, tt := range tests { - want := sErrors.Less(tt.i, tt.j) - if want != tt.want { - if want { - cmpSymbol = '<' - } else { - cmpSymbol = '>' - } - t.Errorf("%s: incorrect less comparison: \"%s\" %c \"%s\"", tt.desc, sErrors[tt.i].s, cmpSymbol, sErrors[tt.j].s) - } - } -} - -type customTestCases struct { - wantEntryPath string - wantEntryCustomTest func(t *testing.T, e *Entry) -} - -func TestOrderedBy(t *testing.T) { - tests := []struct { - name string - inModules map[string]string - testcases []customTestCases - wantErrSubstr string - }{{ - name: "ordered-by user", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - list ordered-list { - key "name"; - ordered-by user; - leaf name { - type string; - } - } - - list unordered-list { - key "name"; - ordered-by system; - leaf name { - type string; - } - } - - list unordered-list2 { - key "name"; - leaf name { - type string; - } - } - - leaf-list ordered-leaflist { - ordered-by user; - type string; - } - - leaf-list unordered-leaflist { - ordered-by system; - type string; - } - - leaf-list unordered-leaflist2 { - type string; - } - } - `, - }, - testcases: []customTestCases{{ - wantEntryPath: "/test/ordered-list", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.ListAttr.OrderedByUser, true; got != want { - t.Errorf("got %v, want %v", got, want) - } - }, - }, { - wantEntryPath: "/test/unordered-list", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.ListAttr.OrderedByUser, false; got != want { - t.Errorf("got %v, want %v", got, want) - } - }, - }, { - wantEntryPath: "/test/unordered-list2", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.ListAttr.OrderedByUser, false; got != want { - t.Errorf("got %v, want %v", got, want) - } - }, - }, { - wantEntryPath: "/test/ordered-leaflist", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.ListAttr.OrderedByUser, true; got != want { - t.Errorf("got %v, want %v", got, want) - } - }, - }, { - wantEntryPath: "/test/unordered-leaflist", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.ListAttr.OrderedByUser, false; got != want { - t.Errorf("got %v, want %v", got, want) - } - }, - }, { - wantEntryPath: "/test/unordered-leaflist2", - wantEntryCustomTest: func(t *testing.T, e *Entry) { - if got, want := e.ListAttr.OrderedByUser, false; got != want { - t.Errorf("got %v, want %v", got, want) - } - }, - }}, - }, { - name: "ordered-by client: invalid argument", - inModules: map[string]string{ - "test.yang": ` - module test { - prefix "t"; - namespace "urn:t"; - - list ordered-list { - key "name"; - ordered-by client; - leaf name { - type string; - } - } - } - `, - }, - wantErrSubstr: "ordered-by has invalid argument", - }} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ms := NewModules() - var errs []error - for n, m := range tt.inModules { - if err := ms.Parse(m, n); err != nil { - errs = append(errs, err) - } - } - - if len(errs) > 0 { - t.Fatalf("ms.Parse(), got unexpected error parsing input modules: %v", errs) - } - - if errs := ms.Process(); len(errs) > 0 { - if len(errs) == 1 { - if diff := errdiff.Substring(errs[0], tt.wantErrSubstr); diff != "" { - t.Fatalf("did not get expected error, %s", diff) - } - return - } - t.Fatalf("ms.Process(), got too many errors processing entries: %v", errs) - } - - dir := map[string]*Entry{} - for _, m := range ms.Modules { - addTreeE(ToEntry(m), dir) - } - - for _, tc := range tt.testcases { - e, ok := dir[tc.wantEntryPath] - if !ok { - t.Fatalf("could not find entry %s within the dir: %v", tc.wantEntryPath, dir) - } - tc.wantEntryCustomTest(t, e) - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yang/file.go b/src/webui/internal/goyang/pkg/yang/file.go deleted file mode 100644 index 48ff76be1..000000000 --- a/src/webui/internal/goyang/pkg/yang/file.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "sort" - "strings" -) - -var ( - // revisionDateSuffixRegex matches on the revision-date portion of a YANG - // file's name. - revisionDateSuffixRegex = regexp.MustCompile(`^@\d{4}-\d{2}-\d{2}\.yang$`) -) - -// PathsWithModules returns all paths under and including the -// root containing files with a ".yang" extension, as well as -// any error encountered -func PathsWithModules(root string) (paths []string, err error) { - pm := map[string]bool{} - filepath.Walk(root, func(p string, info os.FileInfo, e error) error { - err = e - if err == nil { - if info == nil { - return nil - } - if !info.IsDir() && strings.HasSuffix(p, ".yang") { - dir := filepath.Dir(p) - if !pm[dir] { - pm[dir] = true - paths = append(paths, dir) - } - } - return nil - } - return err - }) - return -} - -// AddPath adds the directories specified in p, a colon separated list -// of directory names, to Path, if they are not already in Path. Using -// multiple arguments is also supported. -func (ms *Modules) AddPath(paths ...string) { - for _, path := range paths { - for _, p := range strings.Split(path, ":") { - if !ms.pathMap[p] { - ms.pathMap[p] = true - ms.Path = append(ms.Path, p) - } - } - } -} - -// readFile makes testing of findFile easier. -var readFile = ioutil.ReadFile - -// scanDir makes testing of findFile easier. -var scanDir = findInDir - -// findFile returns the name and contents of the .yang file associated with -// name, or an error. If name is a module name rather than a file name (it does -// not have a .yang extension and there is no / in name), .yang is appended to -// the the name. The directory that the .yang file is found in is added to Path -// if not already in Path. If a file is not found by exact match, directories -// are scanned for "name@revision-date.yang" files, the latest (sorted by -// YYYY-MM-DD revision-date) of these will be selected. -// -// If a path has the form dir/... then dir and all direct or indirect -// subdirectories of dir are searched. -// -// The current directory (.) is always checked first, no matter the value of -// Path. -func (ms *Modules) findFile(name string) (string, string, error) { - slash := strings.Index(name, "/") - if slash < 0 && !strings.HasSuffix(name, ".yang") { - name += ".yang" - if best := scanDir(".", name, false); best != "" { - // we found a matching candidate in the local directory - name = best - } - } - - switch data, err := readFile(name); true { - case err == nil: - ms.AddPath(filepath.Dir(name)) - return name, string(data), nil - case slash >= 0: - // If there are any /'s in the name then don't search Path. - return "", "", fmt.Errorf("no such file: %s", name) - } - - for _, dir := range ms.Path { - var n string - if filepath.Base(dir) == "..." { - n = scanDir(filepath.Dir(dir), name, true) - } else { - n = scanDir(dir, name, false) - } - if n == "" { - continue - } - if data, err := readFile(n); err == nil { - return n, string(data), nil - } - } - return "", "", fmt.Errorf("no such file: %s", name) -} - -// findInDir looks for a file named name in dir or any of its subdirectories if -// recurse is true. if recurse is false, scan only the directory dir. -// If no matching file is found, an empty string is returned. -// -// The file SHOULD have the following name, per -// https://tools.ietf.org/html/rfc7950#section-5.2: -// module-or-submodule-name ['@' revision-date] '.yang' -// where revision-date = 4DIGIT "-" 2DIGIT "-" 2DIGIT -// -// If a perfect name match is found, then that file's path is returned. -// Else if file(s) with otherwise matching names but which contain a -// revision-date pattern exactly matching the above are found, then path of the -// one with the latest date is returned. -func findInDir(dir, name string, recurse bool) string { - fis, err := ioutil.ReadDir(dir) - if err != nil { - return "" - } - - var revisions []string - mname := strings.TrimSuffix(name, ".yang") - for _, fi := range fis { - switch { - case !fi.IsDir(): - if fn := fi.Name(); fn == name { - return filepath.Join(dir, name) - } else if strings.HasPrefix(fn, mname) && revisionDateSuffixRegex.MatchString(strings.TrimPrefix(fn, mname)) { - revisions = append(revisions, fn) - } - case recurse: - if n := findInDir(filepath.Join(dir, fi.Name()), name, recurse); n != "" { - return n - } - } - } - if len(revisions) == 0 { - return "" - } - sort.Strings(revisions) - return filepath.Join(dir, revisions[len(revisions)-1]) -} diff --git a/src/webui/internal/goyang/pkg/yang/file_test.go b/src/webui/internal/goyang/pkg/yang/file_test.go deleted file mode 100644 index 6500a6de4..000000000 --- a/src/webui/internal/goyang/pkg/yang/file_test.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "errors" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "testing" -) - -func TestFindFile(t *testing.T) { - sep := string(os.PathSeparator) - - for _, tt := range []struct { - name string - path []string - check []string - }{ - { - name: "one", - check: []string{"one.yang"}, - }, - { - name: "./two", - check: []string{"./two"}, - }, - { - name: "three.yang", - check: []string{"three.yang"}, - }, - { - name: "four", - path: []string{"dir1", "dir2"}, - check: []string{"four.yang", "dir1" + sep + "four.yang", "dir2" + sep + "four.yang"}, - }, - } { - var checked []string - ms := NewModules() - ms.Path = tt.path - readFile = func(path string) ([]byte, error) { - checked = append(checked, path) - return nil, errors.New("no such file") - } - scanDir = func(dir, name string, recurse bool) string { - return filepath.Join(dir, name) - } - if _, _, err := ms.findFile(tt.name); err == nil { - t.Errorf("%s unexpectedly succeeded", tt.name) - continue - } - if !reflect.DeepEqual(tt.check, checked) { - t.Errorf("%s: got %v, want %v", tt.name, checked, tt.check) - } - } -} - -func TestScanForPathsAndAddModules(t *testing.T) { - // disable any readFile mock setup by other tests - readFile = ioutil.ReadFile - - // Scan the directory tree for YANG modules - paths, err := PathsWithModules("../../testdata") - if err != nil { - t.Fatal(err) - } - // we should have seen two directories being testdata and - // testdata/subdir. - if len(paths) != 2 { - t.Errorf("got %d paths imported, want 2", len(paths)) - } - ms := NewModules() - // add the paths found in the scan to the module path - ms.AddPath(paths...) - - // confirm we can load the four modules that exist in - // the two paths we scanned. - modules := []string{"aug", "base", "other", "subdir1"} - for _, name := range modules { - if _, err := ms.GetModule(name); err != nil { - t.Errorf("getting %s: %v", name, err) - } - } - - // however, a sub module is not a valid argument to GetModule. - if _, err := ms.GetModule("sub"); err == nil { - t.Error("want an error when loading 'sub', got nil") - } - -} - -func TestFindInDir(t *testing.T) { - testDir := "testdata/find-file-test" - - tests := []struct { - desc string - inDir string - inName string - inRecurse bool - want string - }{{ - desc: "file not found", - inDir: testDir, - inName: "green.yang", - inRecurse: true, - want: "", - }, { - desc: "input directory does not exist", - inDir: filepath.Join(testDir, "dne"), - inName: "red.yang", - inRecurse: true, - want: "", - }, { - desc: "exact match", - inDir: testDir, - inName: "blue.yang", - inRecurse: false, - want: filepath.Join(testDir, "blue.yang"), - }, { - desc: "exact match, recursive", - inDir: testDir, - inName: "blue.yang", - inRecurse: true, - want: filepath.Join(testDir, "blue.yang"), - }, { - desc: "exact match with non-standard name", - inDir: testDir, - inName: "non-standard.name", - inRecurse: false, - want: filepath.Join(testDir, "non-standard.name"), - }, { - desc: "revision match without recursion, and ignoring invalid revision", - inDir: testDir, - inName: "red.yang", - inRecurse: false, - want: filepath.Join(testDir, "red@2010-10-10.yang"), - }, { - desc: "revision match with recursion", - inDir: testDir, - inName: "red.yang", - inRecurse: true, - want: filepath.Join(testDir, "dir", "dirdir", "red@2022-02-22.yang"), - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - if got, want := findInDir(tt.inDir, tt.inName, tt.inRecurse), tt.want; got != want { - t.Errorf("got: %q, want: %q", got, want) - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yang/find.go b/src/webui/internal/goyang/pkg/yang/find.go deleted file mode 100644 index 9f0575d47..000000000 --- a/src/webui/internal/goyang/pkg/yang/find.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// This file has functions that search the AST for specified nodes. - -import ( - "reflect" - "strings" -) - -// localPrefix returns the local prefix used by the containing (sub)module to -// refer to its own module. -func localPrefix(n Node) string { - return RootNode(n).GetPrefix() -} - -// trimLocalPrefix trims the current module's prefix from the given name. If the -// name is not prefixed with the local module's prefix or is unprefixed -// entirely, then the same string is returned unchanged. -func trimLocalPrefix(n Node, name string) string { - pfx := localPrefix(n) - if pfx != "" { - pfx += ":" - } - return strings.TrimPrefix(name, pfx) -} - -// FindGrouping finds the grouping named name according to YANG namespace rules -// using the input node as the initial context node. The seen parameter -// provides a list of the modules previously seen by FindGrouping during -// traversal. If the named grouping cannot be found, nil is returned. -// -// FindGrouping works by recursively looking through the context node's parent -// nodes for grouping fields, or in included or imported submodules/modules for -// externally-defined groupings. Note that any prefix in the name must match -// the module prefix of its import statement in the context node's module. -func FindGrouping(n Node, name string, seen map[string]bool) *Grouping { - name = trimLocalPrefix(n, name) - for n != nil { - // Grab the Grouping field of the underlying structure. n is - // always a pointer to a structure, - e := reflect.ValueOf(n).Elem() - v := e.FieldByName("Grouping") - if v.IsValid() { - for _, g := range v.Interface().([]*Grouping) { - if g.Name == name { - return g - } - } - } - v = e.FieldByName("Import") - if v.IsValid() { - for _, i := range v.Interface().([]*Import) { - // If the prefix matches the import statement, - // then search for the trimmed name in that module. - pname := strings.TrimPrefix(name, i.Prefix.Name+":") - if pname == name { - continue - } - if g := FindGrouping(i.Module, pname, seen); g != nil { - return g - } - } - } - v = e.FieldByName("Include") - if v.IsValid() { - for _, i := range v.Interface().([]*Include) { - if seen[i.Module.Name] { - // Prevent infinite loops in the case that we have already looked at - // this submodule. This occurs where submodules have include statements - // in them, or there is a circular dependency. - continue - } - seen[i.Module.Name] = true - if g := FindGrouping(i.Module, name, seen); g != nil { - return g - } - } - } - n = n.ParentNode() - } - return nil -} diff --git a/src/webui/internal/goyang/pkg/yang/find_test.go b/src/webui/internal/goyang/pkg/yang/find_test.go deleted file mode 100644 index da39d5047..000000000 --- a/src/webui/internal/goyang/pkg/yang/find_test.go +++ /dev/null @@ -1,358 +0,0 @@ -package yang - -import ( - "testing" -) - -func TestFindGrouping(t *testing.T) { - tests := []struct { - desc string - inMods map[string]string - inNode func(*Modules) (Node, error) - inName string - wantGroupNodePath string - // wantCannotFound indicates that the grouping cannot be found. - wantCannotFound bool - }{{ - desc: "grouping within module", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } } - - container c { leaf b { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev"], "c") - }, - inName: "g", - wantGroupNodePath: "/dev/g", - }, { - desc: "nested grouping within module", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - - revision 01-01-01 { description "the start of time"; } - - grouping g { grouping gg { leaf a { type string; } } uses gg; } - - container c { leaf b { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev"], "g") - }, - inName: "gg", - wantGroupNodePath: "/dev/g/gg", - }, { - desc: "grouping that uses another grouping both within the same module", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - - revision 01-01-01 { description "the start of time"; } - - grouping gg { leaf a { type string; } } - - grouping g { uses gg; } - - container c { leaf b { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev"], "g") - }, - inName: "gg", - wantGroupNodePath: "/dev/gg", - }, { - desc: "grouping in included submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - container c { leaf b { type string; } } - - revision 01-01-01 { description "the start of time"; } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev"], "c") - }, - inName: "g", - wantGroupNodePath: "/sys/g", - }, { - desc: "grouping in indirectly-included submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - - container c { leaf b { type string; } } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - include sysdb; - - revision 01-01-01 { description "the start of time"; } - }`, - "sysdb": ` - submodule sysdb { - belongs-to dev { - prefix "d"; - } - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev"], "c") - }, - inName: "g", - wantGroupNodePath: "/sysdb/g", - }, { - desc: "grouping in indirectly-included submodule with node in submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - include sysdb; - - revision 01-01-01 { description "the start of time"; } - - container c { leaf b { type string; } } - }`, - "sysdb": ` - submodule sysdb { - belongs-to dev { - prefix "d"; - } - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.SubModules["sys"], "c") - }, - inName: "g", - wantGroupNodePath: "/sysdb/g", - }, { - desc: "grouping in submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - import sysdb { prefix "s"; } - - revision 01-01-01 { description "the start of time"; } - - container c { leaf b { type string; } uses s:g; } - }`, - "sysdb": ` - module sysdb { - prefix sd; - namespace "urn:sd"; - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev"], "c") - }, - inName: "s:g", - wantGroupNodePath: "/sysdb/g", - }, { - desc: "grouping that uses another grouping both in different modules", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - import dev2 { prefix "de2"; } - - revision 01-01-01 { description "the start of time"; } - - container c { leaf l { type string; } uses de2:g; } - }`, - "dev2": ` - module dev2 { - prefix d2; - namespace "urn:d2"; - import dev3 { prefix "de3"; } - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } uses de3:gg; } - }`, - "dev3": ` - module dev3 { - prefix d3; - namespace "urn:d3"; - - revision 01-01-01 { description "the start of time"; } - - grouping gg { leaf b { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev2"], "g") - }, - inName: "de3:gg", - wantGroupNodePath: "/dev3/gg", - }, { - desc: "grouping that uses another grouping both in different modules but prefix is wrong", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - import dev2 { prefix "de2"; } - - revision 01-01-01 { description "the start of time"; } - - container c { leaf l { type string; } uses de2:g; } - }`, - "dev2": ` - module dev2 { - prefix d2; - namespace "urn:d2"; - import dev3 { prefix "de3"; } - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } uses de3:gg; } - }`, - "dev3": ` - module dev3 { - prefix dev3; - namespace "urn:dev3"; - - revision 01-01-01 { description "the start of time"; } - - grouping gg { leaf b { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev2"], "g") - }, - inName: "d3:gg", - wantCannotFound: true, - }, { - desc: "grouping that uses another grouping both in different modules but uses wrong context node", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - import dev2 { prefix "de2"; } - - revision 01-01-01 { description "the start of time"; } - - container c { leaf l { type string; } uses de2:g; } - }`, - "dev2": ` - module dev2 { - prefix d2; - namespace "urn:d2"; - import dev3 { prefix "dev3"; } - - revision 01-01-01 { description "the start of time"; } - - grouping g { leaf a { type string; } uses dev3:gg; } - }`, - "dev3": ` - module dev3 { - prefix dev3; - namespace "urn:dev3"; - - revision 01-01-01 { description "the start of time"; } - - grouping gg { leaf b { type string; } } - }`, - }, - inNode: func(ms *Modules) (Node, error) { - return FindNode(ms.Modules["dev"], "c") - }, - inName: "dev3:gg", - wantCannotFound: true, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - - for n, m := range tt.inMods { - if err := ms.Parse(m, n); err != nil { - t.Fatalf("cannot parse module %s, err: %v", n, err) - } - } - - if errs := ms.Process(); errs != nil { - t.Fatalf("cannot process modules: %v", errs) - } - - seen := map[string]bool{} - node, err := tt.inNode(ms) - if err != nil { - t.Fatalf("cannot find input node: %v", err) - } - g := FindGrouping(node, tt.inName, seen) - if got, want := g == nil, tt.wantCannotFound; got != want { - t.Fatalf("got grouping: %v, wantCannotFound: %v", got, want) - } - if tt.wantCannotFound { - return - } - if got, want := NodePath(g), tt.wantGroupNodePath; got != want { - t.Errorf("found grouping path doesn't match expected, got: %s, want: %s", got, want) - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yang/identity.go b/src/webui/internal/goyang/pkg/yang/identity.go deleted file mode 100644 index 615bff79f..000000000 --- a/src/webui/internal/goyang/pkg/yang/identity.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2016 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "fmt" - "sort" - "sync" -) - -// This file implements data structures and functions that relate to the -// identity type. - -// identityDictionary stores a set of identities across all parsed Modules that -// have been resolved to be identified by their module and name. -type identityDictionary struct { - mu sync.Mutex - // dict is a global cache of identities keyed by - // modulename:identityname, where modulename is the full name of the - // module to which the identity belongs. If the identity were defined - // in a submodule, then the parent module name is used instead. - dict map[string]resolvedIdentity -} - -// resolvedIdentity is an Identity that has been disambiguated. -type resolvedIdentity struct { - Module *Module - Identity *Identity -} - -// isEmpty determines whether the resolvedIdentity struct value is populated. -func (r resolvedIdentity) isEmpty() bool { - return r.Module == nil && r.Identity == nil -} - -// newResolvedIdentity creates a resolved identity from an identity and its -// associated module, and returns the prefixed name (Prefix:IdentityName) -// along with the resolved identity. -func newResolvedIdentity(m *Module, i *Identity) (string, *resolvedIdentity) { - r := &resolvedIdentity{ - Module: m, - Identity: i, - } - return i.modulePrefixedName(), r -} - -func appendIfNotIn(ids []*Identity, chk *Identity) []*Identity { - for _, id := range ids { - if id == chk { - return ids - } - } - return append(ids, chk) -} - -// addChildren adds identity r and all of its children to ids -// deterministically. -func addChildren(r *Identity, ids []*Identity) []*Identity { - ids = appendIfNotIn(ids, r) - - // Iterate through the values of r. - for _, ch := range r.Values { - ids = addChildren(ch, ids) - } - return ids -} - -// findIdentityBase returns the resolved identity that is corresponds to the -// baseStr string in the context of the module/submodule mod. -func (mod *Module) findIdentityBase(baseStr string) (*resolvedIdentity, []error) { - var base resolvedIdentity - var ok bool - var errs []error - - basePrefix, baseName := getPrefix(baseStr) - rootPrefix := mod.GetPrefix() - source := Source(mod) - typeDict := mod.Modules.typeDict - - switch basePrefix { - case "", rootPrefix: - // This is a local identity which is defined within the current - // module - keyName := fmt.Sprintf("%s:%s", module(mod).Name, baseName) - base, ok = typeDict.identities.dict[keyName] - if !ok { - errs = append(errs, fmt.Errorf("%s: can't resolve the local base %s as %s", source, baseStr, keyName)) - } - default: - // This is an identity which is defined within another module - extmod := FindModuleByPrefix(mod, basePrefix) - if extmod == nil { - errs = append(errs, - fmt.Errorf("%s: can't find external module with prefix %s", source, basePrefix)) - break - } - // The identity we are looking for is modulename:basename. - if id, ok := typeDict.identities.dict[fmt.Sprintf("%s:%s", module(extmod).Name, baseName)]; ok { - base = id - break - } - - // Error if we did not find the identity that had the name specified in - // the module it was expected to be in. - if base.isEmpty() { - errs = append(errs, fmt.Errorf("%s: can't resolve remote base %s", source, baseStr)) - } - } - return &base, errs -} - -func (ms *Modules) resolveIdentities() []error { - defer ms.typeDict.identities.mu.Unlock() - ms.typeDict.identities.mu.Lock() - - var errs []error - - // Across all modules, read the identity values that have been extracted - // from them, and compile them into a "fully resolved" map that means that - // we can look them up based on the 'real' prefix of the module and the - // name of the identity. - for _, mod := range ms.Modules { - for _, i := range mod.Identities() { - keyName, r := newResolvedIdentity(mod, i) - ms.typeDict.identities.dict[keyName] = *r - } - - // Hoist up all identities in our included submodules. - // We could just do a range on ms.SubModules, but that - // might process a submodule that no module included. - for _, in := range mod.Include { - if in.Module == nil { - continue - } - for _, i := range in.Module.Identities() { - keyName, r := newResolvedIdentity(in.Module, i) - ms.typeDict.identities.dict[keyName] = *r - } - } - } - - // Now, we want to create for all identities a view of all of their children. - // A child identity here means an inherited identity. - // - // We start by finding the direct children of all identities using the - // 'base' statement. - for _, i := range ms.typeDict.identities.dict { - if i.Identity.Base != nil { - // This identity inherits from one or more other identities. - - root := RootNode(i.Identity) - for _, b := range i.Identity.Base { - base, baseErr := root.findIdentityBase(b.asString()) - - if baseErr != nil { - errs = append(errs, baseErr...) - continue - } - - // Build up a list of direct children of this identity. - base.Identity.Values = append(base.Identity.Values, i.Identity) - } - } - } - - // Now, we can find all transitive identities by recursively populating - // the children of each identity. - for _, i := range ms.typeDict.identities.dict { - newValues := []*Identity{} - for _, j := range i.Identity.Values { - newValues = addChildren(j, newValues) - } - sort.SliceStable(newValues, func(j, k int) bool { - return newValues[j].Name < newValues[k].Name - }) - i.Identity.Values = newValues - } - - return errs -} diff --git a/src/webui/internal/goyang/pkg/yang/identity_test.go b/src/webui/internal/goyang/pkg/yang/identity_test.go deleted file mode 100644 index 631680c9a..000000000 --- a/src/webui/internal/goyang/pkg/yang/identity_test.go +++ /dev/null @@ -1,802 +0,0 @@ -// Copyright 2016 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/openconfig/gnmi/errdiff" -) - -// inputModule is a mock input YANG module. -type inputModule struct { - name string // The filename of the YANG module. - content string // The contents of the YANG module. -} - -type idrefOut struct { - module string // The module that the identityref is within. - name string // The name of the identityref. - values []string // Names of the identities that the identityref relates to. -} - -// identityOut is the output for a particular identity within the test case. -type identityOut struct { - module string // The module that the identity is within. - name string // The name of the identity. - baseNames []string // The base(s) of the identity as string(s). - values []string // The string names of derived identities. -} - -// identityTestCase is a test case for a module which contains identities. -type identityTestCase struct { - name string - in []inputModule // The set of input modules for the test - identities []identityOut // Slice of the identity values expected - idrefs []idrefOut // Slice of identityref results expected - wantErrSubstr string // wanErrSubstr is a substring of the wanted error. -} - -// getBaseNamesFrom is a utility function for getting the base name(s) of an identity -func getBaseNamesFrom(i *Identity) []string { - baseNames := []string{} - for _, base := range i.Base { - baseNames = append(baseNames, base.Name) - } - return baseNames -} - -// Test cases for basic identity extraction. -var basicTestCases = []identityTestCase{ - { - name: "basic-test-case-1: Check identity is found in module.", - in: []inputModule{ - { - name: "idtest-one", - content: ` - module idtest-one { - namespace "urn:idone"; - prefix "idone"; - - identity TEST_ID; - } - `}, - }, - identities: []identityOut{ - {module: "idtest-one", name: "TEST_ID"}, - }, - }, - { - name: "basic-test-case-2: Check identity with base is found in module.", - in: []inputModule{ - { - name: "idtest-two", - content: ` - module idtest-two { - namespace "urn:idtwo"; - prefix "idone"; - - identity TEST_ID; - identity TEST_ID_TWO; - identity TEST_CHILD { - base TEST_ID; - } - } - `}, - }, - identities: []identityOut{ - {module: "idtest-two", name: "TEST_ID"}, - {module: "idtest-two", name: "TEST_ID_TWO"}, - {module: "idtest-two", name: "TEST_CHILD", baseNames: []string{"TEST_ID"}}, - }, - }, - { - name: "basic-test-case-3: Check identity with multiple bases.", - in: []inputModule{ - { - name: "idtest-three", - content: ` - module idtest-three { - namespace "urn:idthree"; - prefix "idthree"; - - identity BASE_ONE; - identity BASE_TWO; - identity TEST_CHILD_WITH_MULTIPLE_BASES { - base BASE_ONE; - base BASE_TWO; - } - } - `}, - }, - identities: []identityOut{ - {module: "idtest-three", name: "BASE_ONE"}, - {module: "idtest-three", name: "BASE_TWO"}, - {module: "idtest-three", name: "TEST_CHILD_WITH_MULTIPLE_BASES", baseNames: []string{"BASE_ONE", "BASE_TWO"}}, - }, - }, - { - name: "basic-test-case-4: Check identity base is found from submodule.", - in: []inputModule{ - { - name: "idtest-one", - content: ` - module idtest-one { - namespace "urn:idone"; - prefix "idone"; - - include "idtest-one-sub"; - - identity TEST_ID_DERIVED { - base TEST_ID; - } - } - `}, - { - name: "idtest-one-sub", - content: ` - submodule idtest-one-sub { - belongs-to idtest-one { - prefix "idone"; - } - - identity TEST_ID; - } - `}, - }, - identities: []identityOut{ - {module: "idtest-one", name: "TEST_ID"}, - {module: "idtest-one", name: "TEST_ID_DERIVED", baseNames: []string{"TEST_ID"}}, - }, - }, - { - name: "basic-test-case-5: Check identity base is found from module.", - in: []inputModule{ - { - name: "idtest-one", - content: ` - module idtest-one { - namespace "urn:idone"; - prefix "idone"; - - include "idtest-one-sub"; - - identity TEST_ID; - } - `}, - { - name: "idtest-one-sub", - content: ` - submodule idtest-one-sub { - belongs-to idtest-one { - prefix "idone"; - } - - identity TEST_ID_DERIVED { - base TEST_ID; - } - } - `}, - }, - identities: []identityOut{ - {module: "idtest-one", name: "TEST_ID_DERIVED", baseNames: []string{"TEST_ID"}}, - {module: "idtest-one", name: "TEST_ID"}, - }, - }, -} - -// Test the ability to extract identities from a module with the correct base -// statements. -func TestIdentityExtract(t *testing.T) { - for _, tt := range basicTestCases { - ms := NewModules() - for _, mod := range tt.in { - _ = ms.Parse(mod.content, mod.name) - } - - for _, ti := range tt.identities { - _, err := ms.GetModule(ti.module) - - if err != nil { - t.Errorf("Could not parse module : %s", ti.module) - continue - } - - foundIdentity := false - var thisID *Identity - for _, ri := range ms.typeDict.identities.dict { - moduleName := module(ri.Module).Name - if ri.Identity.Name == ti.name && moduleName == ti.module { - foundIdentity = true - thisID = ri.Identity - break - } - } - - if !foundIdentity { - t.Errorf("Could not find identity %s in module %s, identity dict:\n%+v", ti.name, ti.module, ms.typeDict.identities.dict) - continue - } - - actualBaseNames := getBaseNamesFrom(thisID) - if len(ti.baseNames) > 0 { - if diff := cmp.Diff(actualBaseNames, ti.baseNames); diff != "" { - t.Errorf("(-got, +want):\n%s", diff) - } - } else { - if thisID.Base != nil { - t.Errorf("Identity %s had unexpected base(s) %s", thisID.Name, - actualBaseNames) - } - } - } - } -} - -// Test cases for validating that identities can be resolved correctly. -var treeTestCases = []identityTestCase{ - { - name: "tree-test-case-0: Validate identity resolution across submodules", - in: []inputModule{ - { - name: "base.yang", - content: ` - module base { - namespace "urn:base"; - prefix "base"; - - include side; - - identity REMOTE_BASE; - } - `}, - { - name: "remote.yang", - content: ` - submodule side { - belongs-to base { - prefix "r"; - } - - identity LOCAL_REMOTE_BASE { - base r:REMOTE_BASE; - } - } - `}, - }, - identities: []identityOut{ - { - module: "base", - name: "REMOTE_BASE", - values: []string{"LOCAL_REMOTE_BASE"}, - }, - }, - }, - { - name: "tree-test-case-1: Validate identity resolution across modules", - in: []inputModule{ - { - name: "base.yang", - content: ` - module base { - namespace "urn:base"; - prefix "base"; - - import remote { prefix "r"; } - import remote2 { prefix "r2"; } - - identity LOCAL_REMOTE_BASE { - base r:REMOTE_BASE; - } - - identity LOCAL_REMOTE_BASE2 { - base r2:REMOTE_BASE2; - } - } - `}, - { - name: "remote.yang", - content: ` - module remote { - namespace "urn:remote"; - prefix "r"; - - identity REMOTE_BASE; - } - `}, - { - name: "remote2.yang", - content: ` - module remote2 { - namespace "urn:remote2"; - prefix "remote"; - - identity REMOTE_BASE2; - } - `}, - }, - identities: []identityOut{ - { - module: "remote", - name: "REMOTE_BASE", - values: []string{"LOCAL_REMOTE_BASE"}, - }, - { - module: "remote2", - name: "REMOTE_BASE2", - values: []string{"LOCAL_REMOTE_BASE2"}, - }, - { - module: "base", - name: "LOCAL_REMOTE_BASE", - baseNames: []string{"r:REMOTE_BASE"}, - }, - { - module: "base", - name: "LOCAL_REMOTE_BASE2", - baseNames: []string{"r2:REMOTE_BASE2"}, - }, - }, - }, - { - name: "tree-test-case-2: Multi-level inheritance validation.", - in: []inputModule{ - { - name: "base.yang", - content: ` - module base { - namespace "urn:base"; - prefix "base"; - - identity GREATGRANDFATHER; - identity GRANDFATHER { - base "GREATGRANDFATHER"; - } - identity FATHER { - base "GRANDFATHER"; - } - identity SON { - base "FATHER"; - } - identity UNCLE { - base "GRANDFATHER"; - } - identity BROTHER { - base "FATHER"; - } - identity GREATUNCLE { - base "GREATGRANDFATHER"; - } - } - `}, - }, - identities: []identityOut{ - { - module: "base", - name: "GREATGRANDFATHER", - values: []string{ - "BROTHER", // Order is alphabetical - "FATHER", - "GRANDFATHER", - "GREATUNCLE", - "SON", - "UNCLE", - }, - }, - { - module: "base", - name: "GRANDFATHER", - baseNames: []string{"GREATGRANDFATHER"}, - values: []string{"BROTHER", "FATHER", "SON", "UNCLE"}, - }, - { - module: "base", - name: "GREATUNCLE", - baseNames: []string{"GREATGRANDFATHER"}, - }, - { - module: "base", - name: "FATHER", - baseNames: []string{"GRANDFATHER"}, - values: []string{"BROTHER", "SON"}, - }, - { - module: "base", - name: "UNCLE", - baseNames: []string{"GRANDFATHER"}, - }, - { - module: "base", - name: "BROTHER", - baseNames: []string{"FATHER"}, - }, - }, - }, - { - name: "tree-test-case-3", - in: []inputModule{ - { - name: "base.yang", - content: ` - module base { - namespace "urn:base"; - prefix "base"; - - identity BASE; - identity NOTBASE { - base BASE; - } - - leaf idref { - type identityref { - base "BASE"; - } - } - } - `}, - }, - identities: []identityOut{ - { - module: "base", - name: "BASE", - values: []string{"NOTBASE"}, - }, - { - module: "base", - name: "NOTBASE", - baseNames: []string{"BASE"}, - }, - }, - idrefs: []idrefOut{ - { - module: "base", - name: "idref", - values: []string{"NOTBASE"}, - }, - }, - }, - { - name: "tree-test-case-4", - in: []inputModule{ - { - name: "base.yang", - content: ` - module base4 { - namespace "urn:base"; - prefix "base4"; - - identity BASE4; - identity CHILD4 { - base BASE4; - } - - typedef t { - type identityref { - base BASE4; - } - } - - leaf tref { - type t; - } - } - `}, - }, - identities: []identityOut{ - { - module: "base4", - name: "BASE4", - values: []string{"CHILD4"}, - }, - { - module: "base4", - name: "CHILD4", - baseNames: []string{"BASE4"}, - }, - }, - idrefs: []idrefOut{ - { - module: "base4", - name: "tref", - values: []string{"CHILD4"}, - }, - }, - }, - { - name: "tree-test-case-5", - in: []inputModule{ - { - name: "base.yang", - content: ` - module base5 { - namespace "urn:base"; - prefix "base5"; - - identity BASE5A; - identity BASE5B; - - identity FIVE_ONE { - base BASE5A; - } - - identity FIVE_TWO { - base BASE5B; - } - - leaf union { - type union { - type identityref { - base BASE5A; - } - type identityref { - base BASE5B; - } - } - } - }`}, - }, - identities: []identityOut{ - { - module: "base5", - name: "BASE5A", - values: []string{"FIVE_ONE"}, - }, - { - module: "base5", - name: "BASE5B", - values: []string{"FIVE_TWO"}, - }, - }, - idrefs: []idrefOut{ - { - module: "base5", - name: "union", - values: []string{"FIVE_ONE", "FIVE_TWO"}, - }, - }, - }, - { - name: "identity's base can't be found", - in: []inputModule{ - { - name: "idtest", - content: ` - module idtest{ - namespace "urn:idtwo"; - prefix "idone"; - - identity TEST_ID_TWO; - identity TEST_CHILD { - base TEST_ID; - } - } - `}, - }, - identities: []identityOut{ - {module: "idtest", name: "TEST_ID2"}, - }, - wantErrSubstr: "can't resolve the local base", - }, - { - name: "identity's base can't be found in remote", - in: []inputModule{ - { - name: "remote.yang", - content: ` - module remote { - namespace "urn:remote"; - prefix "remote"; - - identity REMOTE_BASE_ESCAPE; - } - `}, - { - name: "base.yang", - content: ` - module base { - namespace "urn:base"; - prefix "base"; - - import remote { prefix "r"; } - - identity LOCAL_REMOTE_BASE { - base r:REMOTE_BASE; - } - } - `}, - }, - identities: []identityOut{ - {module: "base", name: "LOCAL_REMOTE_BASE"}, - }, - wantErrSubstr: "can't resolve remote base", - }, - { - name: "identity's base's module can't be found", - in: []inputModule{ - { - name: "remote.yang", - content: ` - module remote { - namespace "urn:remote"; - prefix "remote"; - - identity REMOTE_BASE; - } - `}, - { - name: "base.yang", - content: ` - module base { - namespace "urn:base"; - prefix "base"; - - import remote { prefix "r"; } - - identity LOCAL_REMOTE_BASE { - base roe:REMOTE_BASE; - } - } - `}, - }, - identities: []identityOut{ - {module: "base", name: "LOCAL_REMOTE_BASE"}, - }, - wantErrSubstr: "can't find external module", - }, -} - -// TestIdentityTree - check inheritance of identities from local and remote -// sources. The Values of an Identity correspond to the values that are -// referenced by that identity, which need to be inherited. -func TestIdentityTree(t *testing.T) { - for _, tt := range treeTestCases { - t.Run(tt.name, func(t *testing.T) { - ms := NewModules() - - for _, mod := range tt.in { - _ = ms.Parse(mod.content, mod.name) - } - - errs := ms.Process() - - var err error - switch len(errs) { - case 1: - err = errs[0] - if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Fatalf("%s", diff) - } - return - case 0: - if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Fatalf("%s", diff) - } - default: - t.Fatalf("got multiple errors: %v", errs) - } - - // Walk through the identities that are defined in the test case output - // and validate that they exist, and their base and values are as expected. - for _, chkID := range tt.identities { - m, errs := ms.GetModule(chkID.module) - if errs != nil { - t.Errorf("Couldn't find expected module: %v", errs) - continue - } - - var foundID *Identity - for _, i := range m.Identities { - if i.Name == chkID.name { - foundID = i - break - } - } - - if foundID == nil { - t.Errorf("Couldn't find identity %s in module %s", chkID.name, - chkID.module) - continue - } - - if len(chkID.baseNames) > 0 { - actualBaseNames := getBaseNamesFrom(foundID) - if diff := cmp.Diff(actualBaseNames, chkID.baseNames); diff != "" { - t.Errorf("(-got, +want):\n%s", diff) - } - } - - valueMap := make(map[string]bool) - - for i, val := range chkID.values { - valueMap[val] = false - // Check that IsDefined returns the right result - if !foundID.IsDefined(val) { - t.Errorf("Couldn't find defined value %s for %s", val, chkID.name) - } - - // Check that the values are sorted in a consistent order - if foundID.Values[i].Name != val { - t.Errorf("Invalid order for value #%d. Expecting %s Got %s", i, foundID.Values[i].Name, val) - } - // Check that GetValue returns the right Identity - idval := foundID.GetValue(val) - if idval == nil { - t.Errorf("Couldn't GetValue(%s) for %s", val, chkID.name) - } - } - - // Ensure that IsDefined does not return false positives - if foundID.IsDefined("DoesNotExist") { - t.Errorf("Non-existent value IsDefined for %s", foundID.Name) - } - - if foundID.GetValue("DoesNotExist") != nil { - t.Errorf("Non-existent value GetValue not nil for %s", foundID.Name) - } - - for _, chkv := range foundID.Values { - _, ok := valueMap[chkv.Name] - if !ok { - t.Errorf("Found unexpected value %s for %s", chkv.Name, chkID.name) - continue - } - valueMap[chkv.Name] = true - } - - for k, v := range valueMap { - if v == false { - t.Errorf("Could not find identity %s for %s", k, chkID.name) - } - } - } - - for _, idr := range tt.idrefs { - m, errs := ms.GetModule(idr.module) - if errs != nil { - t.Errorf("Couldn't find expected module %s: %v", idr.module, errs) - continue - } - - if _, ok := m.Dir[idr.name]; !ok { - t.Errorf("Could not find expected identity, got: nil, want: %v", idr.name) - continue - } - - identity := m.Dir[idr.name] - var vals []*Identity - switch len(identity.Type.Type) { - case 0: - vals = identity.Type.IdentityBase.Values - default: - for _, b := range identity.Type.Type { - if b.IdentityBase != nil { - vals = append(vals, b.IdentityBase.Values...) - } - } - } - - var valNames []string - for _, v := range vals { - valNames = append(valNames, v.Name) - } - - if diff := cmp.Diff(idr.values, valNames); diff != "" { - t.Errorf("Identity %s did not have expected values, (-got, +want):\n%s", idr.name, diff) - } - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yang/lex.go b/src/webui/internal/goyang/pkg/yang/lex.go deleted file mode 100644 index 49a0515b2..000000000 --- a/src/webui/internal/goyang/pkg/yang/lex.go +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// This file implements the lexical tokenization of yang. The lexer returns -// a series of tokens with one of the following codes: -// -// tError // an error was encountered -// tEOF // end-of-file -// tString // A de-quoted string (e.g., "\"bob\"" becomes "bob") -// tUnquoted // An un-quoted string -// '{' -// ';' -// '}' - -import ( - "bytes" - "fmt" - "io" - "os" - "reflect" - "runtime" - "strings" - "unicode/utf8" -) - -const ( - eof = 0x7fffffff // end of file, also an invalid rune - maxErrors = 8 - tooMany = "too many errors...\n" -) - -// stateFn represents a state in the lexer as a function, returning the next -// state the lexer should move to. -type stateFn func(*lexer) stateFn - -// A lexer holds the internal state of the lexer. -type lexer struct { - errout io.Writer // destination for errors, defaults to os.Stderr - errcnt int // number of errors encountered - - file string // name of file we are processing - input string // contents of the file - start int // start position in input of unconsumed data. - pos int // current position in the input. - line int // the current line number (1's based) - col int // the current column number (0 based, add 1 before displaying) - - debug bool // set to true to include internal debugging - inPattern bool // set when parsing the argument to a pattern - items chan *token // channel of scanned items. - tcol int // column with tabs expanded (for multi-line strings) - scol int // starting col of current token - sline int // starting line of current token - state stateFn // current state of the lexer - width int // width of last rune read from input. -} - -// A code is a token code. Single character tokens (i.e., punctuation) -// are represented by their unicode code point. -type code int - -const ( - tEOF = code(-1 - iota) // Reached end of file - tError // An error - tString // A dequoted string - tUnquoted // A non-quoted string -) - -// String returns c as a string. -func (c code) String() string { - switch c { - case tError: - return "Error" - case tString: - return "String" - case tUnquoted: - return "Unquoted" - } - if c < 0 || c > '~' { - return fmt.Sprintf("%d", c) - } - return fmt.Sprintf("'%c'", c) -} - -// A token represents one lexical unit read from the input. -// Line and Col are both 1's based. -type token struct { - code code - Text string // the actual text of the token - File string // the source file the token is from - Line int // the source line number the token is from - Col int // the source column number the token is from (8 space tabs) -} - -// Code returns the code of t. If t is nil, tEOF is returned. -func (t *token) Code() code { - if t == nil { - return tEOF - } - return t.code -} - -// String returns the location, code, and text of t as a string. -func (t *token) String() string { - var s []string - if t.File != "" { - s = append(s, t.File+":") - } - if t.Line != 0 { - s = append(s, fmt.Sprintf("%d:%d:", t.Line, t.Col)) - } - if t.Text == "" { - s = append(s, fmt.Sprintf(" %v", t.code)) - } else { - s = append(s, " ", t.Text) - } - return strings.Join(s, "") -} - -// A note on writing to errout. Errors should always be written to errout -// in a single Write call. The test code makes this assumption for testing -// expected errors. - -// newLexer returns a new lexer, importing into it the provided input and path. -// The provided path should indicate where the source originated. -func newLexer(input, path string) *lexer { - // Force input to be newline terminated. - if len(input) > 0 && input[len(input)-1] != '\n' { - input += "\n" - } - return &lexer{ - file: path, - input: input, - line: 1, // humans start with 1 - items: make(chan *token, maxErrors), - state: lexGround, - errout: os.Stderr, - } -} - -// NextToken returns the next token from the input, returning nil on EOF. -func (l *lexer) NextToken() *token { - for { - select { - case item := <-l.items: - return item - default: - if l.state == nil { - return nil - } - if l.debug { - name := runtime.FuncForPC(reflect.ValueOf(l.state).Pointer()).Name() - name = name[strings.LastIndex(name, ".")+1:] - name = strings.TrimPrefix(name, "lex") - input := l.input[l.pos:] - if len(input) > 8 { - input = input[:8] + "..." - } - fmt.Fprintf(os.Stderr, "%d:%d: state %s %q\n", l.line, l.col+1, name, input) - } - l.state = l.state(l) - } - } -} - -// emit emits the currently parsed token marked with code c using emitText. -func (l *lexer) emit(c code) { - l.emitText(c, l.input[l.start:l.pos]) -} - -// emitText emits text as a token marked with c. -// All input up to the current cursor (pos) is consumed. -func (l *lexer) emitText(c code, text string) { - if l.debug { - fmt.Fprintf(os.Stderr, "%v: %q\n", c, text) - } - select { - case l.items <- &token{ - code: c, - Text: text, - File: l.file, - Line: l.sline, - Col: l.scol + 1, - }: - default: - } - l.consume() -} - -// consume consumes all input to the current cursor. -func (l *lexer) consume() { - l.start = l.pos -} - -// backup steps back one rune. It can be called only immediately after a call -// of next. Backing up over a tab will set tcol to the last position of the -// tab, not where the tab started. This is okay as when we call next again it -// will move tcol back to where it was before backup was called. -func (l *lexer) backup() { - l.pos -= l.width - if l.width > 0 { - l.col-- - l.tcol-- - if l.col < 0 { - // We must have backuped up over a newline. - // Don't bother to figure out the column number - // as the next call to next will reset it to 0. - l.line-- - l.col = 0 - l.tcol = 0 - } - } -} - -// peek returns but does not move past the next rune in the input. backup -// is not supported over peeked characters. -func (l *lexer) peek() rune { - rune := l.next() - l.backup() - return rune -} - -// next returns the next rune in the input. If next encounters the end of input -// then it will return eof. -func (l *lexer) next() (rune rune) { - if l.pos >= len(l.input) { - l.width = 0 - return eof - } - // l.width is what limits more than a single backup. - rune, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) - l.pos += l.width - switch rune { - case '\n': - l.line++ - l.col = 0 - l.tcol = 0 - case '\t': - l.tcol = (l.tcol + 8) & ^7 - l.col++ // should this be l.width? - default: - l.tcol++ - l.col++ // should this be l.width? - } - return rune -} - -// acceptRun moves the cursor forward up to, but not including, the first rune -// not found in the valid set. It returns true if any runes were accepted. -func (l *lexer) acceptRun(valid string) bool { - ret := false - for strings.ContainsRune(valid, l.next()) { - ret = true - } - l.backup() - return ret -} - -// skipTo moves the cursor up to, but not including, s. -// Returns whether s was found in the remaining input. -func (l *lexer) skipTo(s string) bool { - if x := strings.Index(l.input[l.pos:], s); x >= 0 { - l.updateCursor(x) - return true - } - return false -} - -// updateCursor moves the cursor forward n bytes. updateCursor does not -// correctly handle tabs. This is okay as it is only used by skipTo, and skipTo -// is never used to skip to an initial " (which is the only time that tcol is -// necessary, as per YANG's multi-line quoted string requirement). -func (l *lexer) updateCursor(n int) { - s := l.input[l.pos : l.pos+n] - l.pos += n - // we could get away without updating width at all because backup is - // only promised to work after a call to next. - l.width = n - - if c := strings.Count(s, "\n"); c > 0 { - l.line += c - l.col = 0 - } - l.col += utf8.RuneCountInString(s[strings.LastIndex(s, "\n")+1:]) -} - -// Errorf writes an error on l.errout and increments the error count. -// If too many errors (8) are encountered then lexing will stop and -// eof is returned as the next token. -func (l *lexer) Errorf(f string, v ...interface{}) { - buf := &bytes.Buffer{} - - if l.debug { - // For internal debugging, print the file and line number - // of the call to Errorf - _, name, line, _ := runtime.Caller(1) - - fmt.Fprintf(buf, "%s:%d: ", name, line) - } - fmt.Fprintf(buf, "%s:%d:%d: ", l.file, l.line, l.col+1) - fmt.Fprintf(buf, f, v...) - b := buf.Bytes() - if b[len(b)-1] != '\n' { - buf.Write([]byte{'\n'}) - } - l.emit(tError) - l.adderror(buf.Bytes()) -} - -func (l *lexer) ErrorfAt(line, col int, f string, v ...interface{}) { - oline, ocol := l.line, l.col - defer func() { - l.line, l.col = oline, ocol - }() - l.line, l.col = line, col - l.Errorf(f, v...) -} - -// adderror writes out the error string err and increases the error count. -// If more than maxErrors are encountered, a "too many errors" message is -// displayed and processing stops (by clearing the input). -func (l *lexer) adderror(err []byte) { - if l.errcnt == maxErrors { - l.pos = 0 - l.start = 0 - l.input = "" - l.errout.Write([]byte(tooMany)) - l.errcnt++ - return - } else if l.errcnt == maxErrors+1 { - return - } - l.errout.Write(err) - l.errcnt++ -} - -// Below are all the states - -// lexGround is the state when the lexer is not in the middle of a token. The -// ground state is left once the start of a token is found. Pure comment lines -// leave the lexer in the ground state. -func lexGround(l *lexer) stateFn { - l.acceptRun(" \t\r\n") // Skip leading spaces - l.consume() - l.sline = l.line - l.scol = l.col - - switch c := l.peek(); c { - case eof: - return nil - case ';', '{', '}': - l.next() - l.emit(code(c)) - return lexGround - case '\'': - l.next() - l.consume() // Toss the leading ' - if !l.skipTo("'") { - l.ErrorfAt(l.line, l.col-1, `missing closing '`) - return nil - } - l.emit(tString) - l.next() // Either EOF or the matching ' - return lexGround - case '"': - l.next() - return lexQString - case '/': - l.next() - switch l.peek() { - case '/': - // Start of a // comment - if !l.skipTo("\n") { - // Here "\n" should always be found, since we force all - // input to be "\n" terminated. - l.ErrorfAt(l.line, l.col-1, `lexer internal error: all lines should be newline-terminated.`) - return nil - } - return lexGround - case '*': - // Start of a /* comment - if !l.skipTo("*/") { - l.ErrorfAt(l.line, l.col-1, `missing closing */`) - return nil - } - // Now actually skip the */ - l.next() - l.next() - return lexGround - default: - return lexUnquoted - } - case '+': - l.next() - switch l.peek() { - case '"', '\'': - l.emit(tUnquoted) - return lexGround - default: - return lexUnquoted - } - default: - return lexUnquoted - } -} - -// From the YANG standard: -// -// If the double-quoted string contains a line break followed by space -// or tab characters that are used to indent the text according to the -// layout in the YANG file, this leading whitespace is stripped from the -// string, up to and including the column of the double quote character, -// or to the first non-whitespace character, whichever occurs first. In -// this process, a tab character is treated as 8 space characters. -// -// If the double-quoted string contains space or tab characters before a -// line break, this trailing whitespace is stripped from the string. - -// lexQString handles double quoted strings, see the above text on how they -// work. The leading " has already been parsed. -func lexQString(l *lexer) stateFn { - indent := l.tcol // the column our text starts on - over := true // set to false when we are not past the indent - - // Keep track of where the starting quote was - line, col := l.line, l.col-1 - - var text []byte - for { - // l.next can return non-8bit unicode code points. - // c cannot be treated as only a single byte. - switch c := l.next(); c { - case eof: - l.ErrorfAt(line, col, `missing closing "`) - return nil - case '"': - l.emitText(tString, string(text)) - - return lexGround - case '\n': - Loop: - // Trim trailing white space from the line. - for i := len(text); i > 0; { - i-- - switch text[i] { - case ' ', '\t': - text = text[:i] - default: - break Loop - } - } - text = append(text, []byte(string(c))...) - over = false - case ' ', '\t': - // Ignore leading white space up to our indent. - if !over && l.tcol <= indent { - break - } - over = true - text = append(text, []byte(string(c))...) - case '\\': - switch c = l.next(); c { - case 'n': - c = '\n' - case 't': - c = '\t' - case '"': - case '\\': - default: - // Strings are use both in descriptions and - // in patterns. In strings only \n, \t, \" - // and \\ are defined. In patterns the \ - // can either mean to escape the character - // (e..g., \{) or to be part of of a special - // sequence such as \S. - if !l.inPattern { - l.ErrorfAt(l.line, l.col-2, `invalid escape sequence: \`+string(c)) - } - text = append(text, '\\') - } - fallthrough - default: - over = true - text = append(text, []byte(string(c))...) - } - } -} - -// lexUnquoted reads one identifier/number/un-quoted-string/... -// -// From https://tools.ietf.org/html/rfc7950#section-6.1.3: -// An unquoted string is any sequence of characters that does not -// contain any space, tab, carriage return, or line feed characters, a -// single or double quote character, a semicolon (";"), braces ("{" or -// "}"), or comment sequences ("//", "/*", or "*/"). -func lexUnquoted(l *lexer) stateFn { - for { - switch c := l.peek(); c { - // TODO: Support detection of comment immediately following an - // unquoted string, likely through supporting two peeks instead - // of just one. - case ' ', '\r', '\n', '\t', ';', '"', '\'', '{', '}', eof: - l.emit(tUnquoted) - return lexGround - default: - l.next() - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/lex_test.go b/src/webui/internal/goyang/pkg/yang/lex_test.go deleted file mode 100644 index 5d1c56a3d..000000000 --- a/src/webui/internal/goyang/pkg/yang/lex_test.go +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "bytes" - "runtime" - "testing" -) - -// line returns the line number from which it was called. -// Used to mark where test entries are in the source. -func line() int { - _, _, line, _ := runtime.Caller(1) - return line - -} - -// Equal returns true if t and tt are equal (have the same code and text), -// false if not. -func (t *token) Equal(tt *token) bool { - return t.code == tt.code && t.Text == tt.Text -} - -// T Creates a new token from the provided code and string. -func T(c code, text string) *token { return &token{code: c, Text: text} } - -func TestLex(t *testing.T) { -Tests: - for _, tt := range []struct { - line int - in string - tokens []*token - }{ - {line(), "", nil}, - {line(), "bob", []*token{ - T(tUnquoted, "bob"), - }}, - {line(), "bob //bob", []*token{ - T(tUnquoted, "bob"), - }}, - {line(), "/the/path", []*token{ - T(tUnquoted, "/the/path"), - }}, - {line(), "+the/path", []*token{ - T(tUnquoted, "+the/path"), - }}, - {line(), "+the+path", []*token{ - T(tUnquoted, "+the+path"), - }}, - {line(), "+ the/path", []*token{ - T(tUnquoted, "+"), - T(tUnquoted, "the/path"), - }}, - {line(), "{bob}", []*token{ - T('{', "{"), - T(tUnquoted, "bob"), - T('}', "}"), - }}, - {line(), "bob;fred", []*token{ - T(tUnquoted, "bob"), - T(';', ";"), - T(tUnquoted, "fred"), - }}, - {line(), "\t bob\t; fred ", []*token{ - T(tUnquoted, "bob"), - T(';', ";"), - T(tUnquoted, "fred"), - }}, - {line(), ` - bob; - fred -`, []*token{ - T(tUnquoted, "bob"), - T(';', ";"), - T(tUnquoted, "fred"), - }}, - {line(), ` - // This is a comment - bob; - fred -`, []*token{ - T(tUnquoted, "bob"), - T(';', ";"), - T(tUnquoted, "fred"), - }}, - {line(), ` - /* This is a comment */ - bob; - fred -`, []*token{ - T(tUnquoted, "bob"), - T(';', ";"), - T(tUnquoted, "fred"), - }}, - {line(), ` - /* - * This is a comment - */ - bob; - fred -`, []*token{ - T(tUnquoted, "bob"), - T(';', ";"), - T(tUnquoted, "fred"), - }}, - {line(), ` - bob; // This is bob - fred // This is fred -`, []*token{ - T(tUnquoted, "bob"), - T(';', ";"), - T(tUnquoted, "fred"), - }}, - {line(), ` -pattern '[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+'; -`, []*token{ - T(tUnquoted, "pattern"), - T(tString, "[a-zA-Z0-9!#$%&"), - T(tUnquoted, "+"), - T(tString, "'"), - T(tUnquoted, "+"), - T(tString, "*+/=?^_`{|}~-]+"), - T(';', ";"), - }}, - {line(), ` -// tab indent both lines - "Broken - line" -`, []*token{ - T(tString, "Broken\nline"), - }}, - {line(), ` -// tab indent both lines, trailing spaces and tabs - "Broken - line" -`, []*token{ - T(tString, "Broken\nline"), - }}, - {line(), ` -// tab indent first line, spaces and tab second line - "Broken - line" -`, []*token{ - T(tString, "Broken\nline"), - }}, - {line(), ` -// tab indent first line, spaces second linfe - "Broken - line" -`, []*token{ - T(tString, "Broken\nline"), - }}, - {line(), ` -// extra space in second line - "Broken - space" -`, []*token{ - T(tString, "Broken\n space"), - }}, - {line(), ` -// spaces first line, tab on second - "Broken - space" -`, []*token{ - T(tString, "Broken\nspace"), - }}, - {line(), ` -// Odd indenting - "Broken - space" -`, []*token{ - T(tString, "Broken\nspace"), - }}, - {line(), ` -// Odd indenting - "Broken \t - space with trailing space" -`, []*token{ - T(tString, "Broken\nspace with trailing space"), - }}, - } { - l := newLexer(tt.in, "") - // l.debug = true - for i := 0; ; i++ { - token := l.NextToken() - if token == nil { - if len(tt.tokens) != i { - t.Errorf("%d: got %d tokens, want %d", tt.line, i, len(tt.tokens)) - } - continue Tests - } - if len(tt.tokens) > i && !token.Equal(tt.tokens[i]) { - t.Errorf("%d, %d: got (%v, %q) want (%v, %q)", tt.line, i, token.code, token.Text, tt.tokens[i].code, tt.tokens[i].Text) - } - } - } -} - -func TestLexErrors(t *testing.T) { - for _, tt := range []struct { - line int - in string - errcnt int - errs string - }{ - {line(), - `1: "no closing quote`, - 1, - `test.yang:1:4: missing closing " -`, - }, - {line(), - `1: on another line -2: there is "no closing quote\"`, - 1, - `test.yang:2:13: missing closing " -`, - }, - {line(), - `1: -2: "Mares eat oats," -3: "And does eat oats," -4: "But little lambs eat ivy," -5: "and if I were a little lamb," -6: "I'ld eat ivy too. -5: So saith the sage.`, - 1, - `test.yang:6:4: missing closing " -`, - }, - {line(), - `1: -2: "Quoted string" -3: "Missing quote -4: "Another quoted string" -`, - 1, - `test.yang:4:26: missing closing " -`, - }, - {line(), - `1: -2: 'Quoted string' -3: 'Missing quote -4: 'Another quoted string' -`, - 1, - `test.yang:4:26: missing closing ' -`, - }, - {line(), - `1: "Quoted string\" -2: Missing end-quote\q`, - 2, - `test.yang:2:21: invalid escape sequence: \q -test.yang:1:4: missing closing " -`, - }, - {line(), - `/* This is a comment -without an ending. -`, - 1, - `test.yang:1:1: missing closing */ -`, - }, - {line(), - // Two errors too many. - `yang-version 1.1;description "\/\/\/\/\/\/\/\/\/\/";`, - 9, - `test.yang:1:31: invalid escape sequence: \/ -test.yang:1:33: invalid escape sequence: \/ -test.yang:1:35: invalid escape sequence: \/ -test.yang:1:37: invalid escape sequence: \/ -test.yang:1:39: invalid escape sequence: \/ -test.yang:1:41: invalid escape sequence: \/ -test.yang:1:43: invalid escape sequence: \/ -test.yang:1:45: invalid escape sequence: \/ -` + tooMany, - }, - } { - l := newLexer(tt.in, "test.yang") - errbuf := &bytes.Buffer{} - l.errout = errbuf - for l.NextToken() != nil { - - } - if l.errcnt != tt.errcnt { - t.Errorf("%d: got %d errors, want %v", tt.line, l.errcnt, tt.errcnt) - } - errs := errbuf.String() - if errs != tt.errs { - t.Errorf("%d: got errors:\n%s\nwant:\n%s", tt.line, errs, tt.errs) - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/marshal_test.go b/src/webui/internal/goyang/pkg/yang/marshal_test.go deleted file mode 100644 index 6151aa150..000000000 --- a/src/webui/internal/goyang/pkg/yang/marshal_test.go +++ /dev/null @@ -1,832 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/kylelemons/godebug/pretty" -) - -func TestMarshalJSON(t *testing.T) { - tests := []struct { - name string - in *Entry - want string - wantErr bool - }{{ - name: "simple leaf entry", - in: &Entry{ - Name: "leaf", - Node: &Leaf{ - Name: "leaf", - }, - Description: "This is a fake leaf.", - Default: []string{"default-leaf-value"}, - Errors: []error{fmt.Errorf("error one")}, - Kind: LeafEntry, - Config: TSTrue, - Prefix: &Value{ - Name: "ModulePrefix", - Source: &Statement{ - Keyword: "prefix", - Argument: "ModulePrefix", - HasArgument: true, - }, - }, - Type: &YangType{ - Name: "string", - Kind: Ystring, - Default: "string-value", - }, - Annotation: map[string]interface{}{ - "fish": struct{ Side string }{"chips"}, - }, - }, - want: `{ - "Name": "leaf", - "Description": "This is a fake leaf.", - "Default": [ - "default-leaf-value" - ], - "Kind": 0, - "Config": 1, - "Prefix": { - "Name": "ModulePrefix", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "ModulePrefix" - } - }, - "Type": { - "Name": "string", - "Kind": 18, - "Default": "string-value" - }, - "Annotation": { - "fish": { - "Side": "chips" - } - } -}`, - }, { - name: "simple container entry with parent", - in: &Entry{ - Name: "container", - Node: &Container{ - Name: "container", - }, - Kind: DirectoryEntry, - Config: TSFalse, - Prefix: &Value{ - Name: "ModulePrefix", - Source: &Statement{ - Keyword: "prefix", - Argument: "ModulePrefix", - HasArgument: true, - }, - }, - Dir: map[string]*Entry{ - "child": { - Name: "leaf", - Node: &Leaf{ - Name: "leaf", - }, - Kind: LeafEntry, - Config: TSUnset, - Prefix: &Value{ - Name: "ModulePrefix", - Source: &Statement{ - Keyword: "prefix", - Argument: "ModulePrefix", - HasArgument: true, - }, - }, - Type: &YangType{ - Name: "union", - Type: []*YangType{{ - Name: "string", - Pattern: []string{"^a.*$"}, - Kind: Ystring, - Length: YangRange{{ - Min: FromInt(10), - Max: FromInt(20), - }}, - }}, - }, - }, - }, - Augments: []*Entry{{ - Name: "augment", - Node: &Leaf{ - Name: "leaf", - }, - Kind: LeafEntry, - Config: TSFalse, - Prefix: &Value{ - Name: "ModulePrefix", - Source: &Statement{ - Keyword: "prefix", - Argument: "ModulePrefix", - HasArgument: true, - }, - }, - }}, - Augmented: []*Entry{{ - Name: "augmented", - Node: &Leaf{ - Name: "leaf", - }, - Kind: LeafEntry, - Config: TSTrue, - Prefix: &Value{ - Name: "ModulePrefix", - Source: &Statement{ - Keyword: "prefix", - Argument: "ModulePrefix", - HasArgument: true, - }, - }, - }}, - Uses: []*UsesStmt{{ - Uses: &Uses{ - Name: "grouping", - }, - Grouping: &Entry{ - Name: "grouping", - Node: &Grouping{ - Name: "grouping", - Leaf: []*Leaf{{ - Name: "groupingLeaf", - }}, - }, - Config: TSFalse, - Prefix: &Value{ - Name: "ModulePrefix", - Source: &Statement{ - Keyword: "prefix", - Argument: "ModulePrefix", - HasArgument: true, - }, - }, - }, - }}, - }, - want: `{ - "Name": "container", - "Kind": 1, - "Config": 2, - "Prefix": { - "Name": "ModulePrefix", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "ModulePrefix" - } - }, - "Dir": { - "child": { - "Name": "leaf", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "ModulePrefix", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "ModulePrefix" - } - }, - "Type": { - "Name": "union", - "Kind": 0, - "Type": [ - { - "Name": "string", - "Kind": 18, - "Length": [ - { - "Min": { - "Value": 10, - "FractionDigits": 0, - "Negative": false - }, - "Max": { - "Value": 20, - "FractionDigits": 0, - "Negative": false - } - } - ], - "Pattern": [ - "^a.*$" - ] - } - ] - } - } - }, - "Augments": [ - { - "Name": "augment", - "Kind": 0, - "Config": 2, - "Prefix": { - "Name": "ModulePrefix", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "ModulePrefix" - } - } - } - ], - "Augmented": [ - { - "Name": "augmented", - "Kind": 0, - "Config": 1, - "Prefix": { - "Name": "ModulePrefix", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "ModulePrefix" - } - } - } - ], - "Uses": [ - { - "Uses": { - "Name": "grouping" - }, - "Grouping": { - "Name": "grouping", - "Kind": 0, - "Config": 2, - "Prefix": { - "Name": "ModulePrefix", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "ModulePrefix" - } - } - } - } - ] -}`, - }, { - name: "Entry with list and leaflist", - in: &Entry{ - Name: "list", - Kind: DirectoryEntry, - Config: TSUnset, - Dir: map[string]*Entry{ - "leaf": { - Name: "string", - Kind: LeafEntry, - }, - "leaf-list": { - Name: "leaf-list", - ListAttr: &ListAttr{ - MaxElements: 18446744073709551615, - MinElements: 0, - }, - }, - }, - ListAttr: &ListAttr{ - MaxElements: 42, - MinElements: 48, - }, - Identities: []*Identity{{ - Name: "ID_ONE", - }}, - Exts: []*Statement{{ - Keyword: "some-extension:ext", - Argument: "ext-value", - HasArgument: true, - }}, - }, - want: `{ - "Name": "list", - "Kind": 1, - "Config": 0, - "Dir": { - "leaf": { - "Name": "string", - "Kind": 0, - "Config": 0 - }, - "leaf-list": { - "Name": "leaf-list", - "Kind": 0, - "Config": 0, - "ListAttr": { - "MinElements": 0, - "MaxElements": 18446744073709551615, - "OrderedBy": null, - "OrderedByUser": false - } - } - }, - "Exts": [ - { - "Keyword": "some-extension:ext", - "HasArgument": true, - "Argument": "ext-value" - } - ], - "ListAttr": { - "MinElements": 48, - "MaxElements": 42, - "OrderedBy": null, - "OrderedByUser": false - }, - "Identities": [ - { - "Name": "ID_ONE" - } - ] -}`, - }} - - for _, tt := range tests { - got, err := json.MarshalIndent(tt.in, "", " ") - if err != nil { - if !tt.wantErr { - t.Errorf("%s: json.MarshalIndent(%v, ...): got unexpected error: %v", tt.name, tt.in, err) - } - continue - } - - if diff := pretty.Compare(string(got), tt.want); diff != "" { - t.Errorf("%s: jsonMarshalIndent(%v, ...): did not get expected JSON, diff(-got,+want):\n%s", tt.name, tt.in, diff) - } - } -} - -func TestParseAndMarshal(t *testing.T) { - tests := []struct { - name string - in []inputModule - want map[string]string - }{{ - name: "simple single module", - in: []inputModule{{ - name: "test.yang", - content: `module test { - prefix "t"; - namespace "urn:t"; - - typedef foobar { - type string { - length "10"; - } - } - - identity "BASE"; - identity "DERIVED" { base "BASE"; } - - container test { - list a { - key "k"; - min-elements 10; - max-elements "unbounded"; - leaf k { type string; } - - leaf bar { - type foobar; - } - } - - leaf d { - type decimal64 { - fraction-digits 8; - } - } - - leaf-list zip { - type string; - } - - leaf-list zip2 { - max-elements 1000; - type string; - } - - leaf x { - type union { - type string; - type identityref { - base "BASE"; - } - } - } - } - }`, - }}, - want: map[string]string{ - "test": `{ - "Name": "test", - "Kind": 1, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Dir": { - "test": { - "Name": "test", - "Kind": 1, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Dir": { - "a": { - "Name": "a", - "Kind": 1, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Dir": { - "bar": { - "Name": "bar", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Type": { - "Name": "foobar", - "Kind": 18, - "Length": [ - { - "Min": { - "Value": 10, - "FractionDigits": 0, - "Negative": false - }, - "Max": { - "Value": 10, - "FractionDigits": 0, - "Negative": false - } - } - ] - } - }, - "k": { - "Name": "k", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Type": { - "Name": "string", - "Kind": 18 - } - } - }, - "Key": "k", - "ListAttr": { - "MinElements": 10, - "MaxElements": 18446744073709551615, - "OrderedBy": null, - "OrderedByUser": false - } - }, - "d": { - "Name": "d", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Type": { - "Name": "decimal64", - "Kind": 12, - "FractionDigits": 8, - "Range": [ - { - "Min": { - "Value": 9223372036854775808, - "FractionDigits": 8, - "Negative": true - }, - "Max": { - "Value": 9223372036854775807, - "FractionDigits": 8, - "Negative": false - } - } - ] - } - }, - "x": { - "Name": "x", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Type": { - "Name": "union", - "Kind": 19, - "Type": [ - { - "Name": "string", - "Kind": 18 - }, - { - "Name": "identityref", - "Kind": 15, - "IdentityBase": { - "Name": "BASE", - "Values": [ - { - "Name": "DERIVED" - } - ] - } - } - ] - } - }, - "zip": { - "Name": "zip", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Type": { - "Name": "string", - "Kind": 18 - }, - "ListAttr": { - "MinElements": 0, - "MaxElements": 18446744073709551615, - "OrderedBy": null, - "OrderedByUser": false - } - }, - "zip2": { - "Name": "zip2", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Type": { - "Name": "string", - "Kind": 18 - }, - "ListAttr": { - "MinElements": 0, - "MaxElements": 1000, - "OrderedBy": null, - "OrderedByUser": false - } - } - } - } - }, - "Identities": [ - { - "Name": "BASE", - "Values": [ - { - "Name": "DERIVED" - } - ] - }, - { - "Name": "DERIVED" - } - ], - "extra-unstable": { - "namespace": [ - { - "Name": "urn:t", - "Source": { - "Keyword": "namespace", - "HasArgument": true, - "Argument": "urn:t" - } - } - ] - } -}`, - }, - }, { - name: "multiple modules with extension", - in: []inputModule{{ - name: "ext.yang", - content: `module ext { - prefix "e"; - namespace "urn:e"; - - extension foobar { - argument "baz"; - } - }`, - }, { - name: "test.yang", - content: `module test { - prefix "t"; - namespace "urn:t"; - - import ext { prefix ext; } - - leaf t { - type string; - ext:foobar "marked"; - } - }`, - }}, - want: map[string]string{ - "test": `{ - "Name": "test", - "Kind": 1, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Dir": { - "t": { - "Name": "t", - "Kind": 0, - "Config": 0, - "Prefix": { - "Name": "t", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "t" - } - }, - "Type": { - "Name": "string", - "Kind": 18 - }, - "Exts": [ - { - "Keyword": "ext:foobar", - "HasArgument": true, - "Argument": "marked" - } - ] - } - }, - "extra-unstable": { - "namespace": [ - { - "Name": "urn:t", - "Source": { - "Keyword": "namespace", - "HasArgument": true, - "Argument": "urn:t" - } - } - ] - } -}`, - "ext": `{ - "Name": "ext", - "Kind": 1, - "Config": 0, - "Prefix": { - "Name": "e", - "Source": { - "Keyword": "prefix", - "HasArgument": true, - "Argument": "e" - } - }, - "extra-unstable": { - "extension": [ - { - "Name": "foobar", - "Argument": { - "Name": "baz" - } - } - ], - "namespace": [ - { - "Name": "urn:e", - "Source": { - "Keyword": "namespace", - "HasArgument": true, - "Argument": "urn:e" - } - } - ] - } -}`, - }, - }} - - for _, tt := range tests { - ms := NewModules() - - for _, mod := range tt.in { - if err := ms.Parse(mod.content, mod.name); err != nil { - t.Errorf("%s: ms.Parse(..., %v): parsing error with module: %v", tt.name, mod.name, err) - continue - } - - if errs := ms.Process(); len(errs) != 0 { - t.Errorf("%s: ms.Process(): could not parse modules: %v", tt.name, errs) - continue - } - - entries := make(map[string]*Entry) - for _, m := range ms.Modules { - if _, ok := entries[m.Name]; !ok { - entries[m.Name] = ToEntry(m) - - got, err := json.MarshalIndent(entries[m.Name], "", " ") - if err != nil { - t.Errorf("%s: json.MarshalIndent(...): got unexpected error: %v", tt.name, err) - continue - } - - if diff := cmp.Diff(string(got), tt.want[m.Name]); diff != "" { - t.Errorf("%s: json.MarshalIndent(...): did not get expected JSON, diff(-got,+want):\n%s", tt.name, diff) - } - } - } - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/modules.go b/src/webui/internal/goyang/pkg/yang/modules.go deleted file mode 100644 index ab543d259..000000000 --- a/src/webui/internal/goyang/pkg/yang/modules.go +++ /dev/null @@ -1,466 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// This file implements the Modules type. This includes the processing of -// include and import statements, which must be done prior to turning the -// module into an Entry tree. - -import ( - "fmt" - "sync" -) - -// Modules contains information about all the top level modules and -// submodules that are read into it via its Read method. -type Modules struct { - Modules map[string]*Module // All "module" nodes - SubModules map[string]*Module // All "submodule" nodes - includes map[*Module]bool // Modules we have already done include on - nsMu sync.Mutex // nsMu protects the byNS map. - byNS map[string]*Module // Cache of namespace lookup - typeDict *typeDictionary // Cache for type definitions. - entryCacheMu sync.RWMutex // entryCacheMu protects the entryCache map. - // entryCache is used to prevent unnecessary recursion into previously - // converted nodes. To access the map, use the get/set/ClearEntryCache() - // thread-safe functions. - entryCache map[Node]*Entry - // mergedSubmodule is used to prevent re-parsing a submodule that has already - // been merged into a particular entity when circular dependencies are being - // ignored. The keys of the map are a string that is formed by concatenating - // the name of the including (sub)module and the included submodule. - mergedSubmodule map[string]bool - // ParseOptions sets the options for the current YANG module parsing. It can be - // directly set by the caller to influence how goyang will behave in the presence - // of certain exceptional cases. - ParseOptions Options - // Path is the list of directories to look for .yang files in. - Path []string - // pathMap is used to prevent adding dups in Path. - pathMap map[string]bool -} - -// NewModules returns a newly created and initialized Modules. -func NewModules() *Modules { - ms := &Modules{ - Modules: map[string]*Module{}, - SubModules: map[string]*Module{}, - includes: map[*Module]bool{}, - byNS: map[string]*Module{}, - typeDict: newTypeDictionary(), - mergedSubmodule: map[string]bool{}, - entryCache: map[Node]*Entry{}, - pathMap: map[string]bool{}, - } - return ms -} - -// Read reads the named yang module into ms. The name can be the name of an -// actual .yang file or a module/submodule name (the base name of a .yang file, -// e.g., foo.yang is named foo). An error is returned if the file is not -// found or there was an error parsing the file. -func (ms *Modules) Read(name string) error { - name, data, err := ms.findFile(name) - if err != nil { - return err - } - return ms.Parse(data, name) -} - -// Parse parses data as YANG source and adds it to ms. The name should reflect -// the source of data. -// Note: If an error is returned, valid modules might still have been added to -// the Modules cache. -func (ms *Modules) Parse(data, name string) error { - ss, err := Parse(data, name) - if err != nil { - return err - } - for _, s := range ss { - n, err := buildASTWithTypeDict(s, ms.typeDict) - if err != nil { - return err - } - if err := ms.add(n); err != nil { - return err - } - } - return nil -} - -// GetModule returns the Entry of the module named by name. GetModule will -// search for and read the file named name + ".yang" if it cannot satisfy the -// request from what it has currently read. -// -// GetModule is a convenience function for calling Read and Process, and -// then looking up the module name. It is safe to call Read and Process prior -// to calling GetModule. -func (ms *Modules) GetModule(name string) (*Entry, []error) { - if ms.Modules[name] == nil { - if err := ms.Read(name); err != nil { - return nil, []error{err} - } - if ms.Modules[name] == nil { - return nil, []error{fmt.Errorf("module not found: %s", name)} - } - } - // Make sure that the modules have all been processed and have no - // errors. - if errs := ms.Process(); len(errs) != 0 { - return nil, errs - } - return ToEntry(ms.Modules[name]), nil -} - -// GetModule optionally reads in a set of YANG source files, named by sources, -// and then returns the Entry for the module named module. If sources is -// missing, or the named module is not yet known, GetModule searches for name -// with the suffix ".yang". GetModule either returns an Entry or returns -// one or more errors. -// -// GetModule is a convenience function for calling NewModules, Read, and Process, -// and then looking up the module name. -func GetModule(name string, sources ...string) (*Entry, []error) { - var errs []error - ms := NewModules() - for _, source := range sources { - if err := ms.Read(source); err != nil { - errs = append(errs, err) - } - } - if len(errs) > 0 { - return nil, errs - } - return ms.GetModule(name) -} - -// add adds Node n to ms. n must be assignable to *Module (i.e., it is a -// "module" or "submodule"). An error is returned if n is a duplicate of -// a name already added, or n is not assignable to *Module. -func (ms *Modules) add(n Node) error { - var m map[string]*Module - - name := n.NName() - kind := n.Kind() - switch kind { - case "module": - m = ms.Modules - case "submodule": - m = ms.SubModules - default: - return fmt.Errorf("not a module or submodule: %s is of type %s", name, kind) - } - - mod := n.(*Module) - fullName := mod.FullName() - mod.Modules = ms - - if o := m[fullName]; o != nil { - return fmt.Errorf("duplicate %s %s at %s and %s", kind, fullName, Source(o), Source(n)) - } - m[fullName] = mod - if fullName == name { - return nil - } - - // Add us to the map if: - // name has not been added before - // fullname is a more recent version of the entry. - if o := m[name]; o == nil || o.FullName() < fullName { - m[name] = mod - } - return nil -} - -// FindModule returns the Module/Submodule specified by n, which must be a -// *Include or *Import. If n is a *Include then a submodule is returned. If n -// is a *Import then a module is returned. -func (ms *Modules) FindModule(n Node) *Module { - name := n.NName() - rev := name - var m map[string]*Module - - switch i := n.(type) { - case *Include: - m = ms.SubModules - if i.RevisionDate != nil { - rev = name + "@" + i.RevisionDate.Name - } - // TODO(borman): we should check the BelongsTo field below? - case *Import: - m = ms.Modules - if i.RevisionDate != nil { - rev = name + "@" + i.RevisionDate.Name - } - default: - return nil - } - if n := m[rev]; n != nil { - return n - } - if n := m[name]; n != nil { - return n - } - - // Try to read first a module by revision - if err := ms.Read(rev); err != nil { - // if failed, try to read a module by its bare name - if err := ms.Read(name); err != nil { - return nil - } - } - if n := m[rev]; n != nil { - return n - } - return m[name] -} - -// FindModuleByNamespace either returns the Module specified by the namespace -// or returns an error. -func (ms *Modules) FindModuleByNamespace(ns string) (*Module, error) { - // Protect the byNS map from concurrent accesses - ms.nsMu.Lock() - defer ms.nsMu.Unlock() - - if m, ok := ms.byNS[ns]; ok { - return m, nil - } - var found *Module - for _, m := range ms.Modules { - if m.Namespace.Name == ns { - switch { - case m == found: - case found != nil: - return nil, fmt.Errorf("namespace %s matches two or more modules (%s, %s)", - ns, found.Name, m.Name) - default: - found = m - } - } - } - if found == nil { - return nil, fmt.Errorf("%q: no such namespace", ns) - } - // Don't cache negative results because new modules could be added. - ms.byNS[ns] = found - return found, nil -} - -// process satisfies all include and import statements and verifies that all -// link ref paths reference a known node. If an import or include references -// a [sub]module that is not already known, Process will search for a .yang -// file that contains it, returning an error if not found. An error is also -// returned if there is an unknown link ref path or other parsing errors. -// -// Process must be called once all the source modules have been read in and -// prior to converting Node tree into an Entry tree. -func (ms *Modules) process() []error { - var mods []*Module - var errs []error - - // Collect the list of modules we know about now so when we range - // below we don't pick up new modules. We assume the user tells - // us explicitly which modules they are interested in. - for _, m := range ms.Modules { - mods = append(mods, m) - } - for _, m := range mods { - if err := ms.include(m); err != nil { - errs = append(errs, err) - } - } - - // Resolve identities before resolving typedefs, otherwise when we resolve a - // typedef that has an identityref within it, then the identity dictionary - // has not yet been built. - errs = append(errs, ms.resolveIdentities()...) - // Append any errors found trying to resolve typedefs - errs = append(errs, ms.typeDict.resolveTypedefs()...) - - return errs -} - -// Process processes all the modules and submodules that have been read into -// ms. While processing, if an include or import is found for which there -// is no matching module, Process attempts to locate the source file (using -// Path) and automatically load them. If a file cannot be found then an -// error is returned. When looking for a source file, Process searches for a -// file using the module's or submodule's name with ".yang" appended. After -// searching the current directory, the directories in Path are searched. -// -// Process builds Entry trees for each modules and submodules in ms. These -// trees are accessed using the ToEntry function. Process does augmentation -// on Entry trees once all the modules and submodules in ms have been built. -// Following augmentation, Process inserts implied case statements. I.e., -// -// choice interface-type { -// container ethernet { ... } -// } -// -// has a case statement inserted to become: -// -// choice interface-type { -// case ethernet { -// container ethernet { ... } -// } -// } -// -// Process may return multiple errors if multiple errors were encountered -// while processing. Even though multiple errors may be returned, this does -// not mean these are all the errors. Process will terminate processing early -// based on the type and location of the error. -func (ms *Modules) Process() []error { - // Reset globals that may remain stale if multiple Process() calls are - // made by the same caller. - ms.mergedSubmodule = map[string]bool{} - ms.ClearEntryCache() - - errs := ms.process() - if len(errs) > 0 { - return errorSort(errs) - } - - for _, m := range ms.Modules { - errs = append(errs, ToEntry(m).GetErrors()...) - } - for _, m := range ms.SubModules { - errs = append(errs, ToEntry(m).GetErrors()...) - } - - if len(errs) > 0 { - return errorSort(errs) - } - - // Now handle all the augments. We don't have a good way to know - // what order to process them in, so repeat until no progress is made - - mods := make([]*Module, 0, len(ms.Modules)+len(ms.SubModules)) - for _, m := range ms.Modules { - mods = append(mods, m) - } - for _, m := range ms.SubModules { - mods = append(mods, m) - } - for len(mods) > 0 { - var processed int - for i := 0; i < len(mods); { - m := mods[i] - p, s := ToEntry(m).Augment(false) - processed += p - if s == 0 { - mods[i] = mods[len(mods)-1] - mods = mods[:len(mods)-1] - continue - } - i++ - } - if processed == 0 { - break - } - } - - // Now fix up all the choice statements to add in the missing case - // statements. - for _, m := range ms.Modules { - ToEntry(m).FixChoice() - } - for _, m := range ms.SubModules { - ToEntry(m).FixChoice() - } - - // Go through any modules that have remaining augments and collect - // the errors. - for _, m := range mods { - ToEntry(m).Augment(true) - errs = append(errs, ToEntry(m).GetErrors()...) - } - - // The deviation statement is only valid under a module or submodule, - // which allows us to avoid having to process it within ToEntry, and - // rather we can just walk all modules and submodules *after* entries - // are resolved. This means we do not need to concern ourselves that - // an entry does not exist. - dvP := map[string]bool{} // cache the modules we've handled since we have both modname and modname@revision-date - for _, devmods := range []map[string]*Module{ms.Modules, ms.SubModules} { - for _, m := range devmods { - e := ToEntry(m) - if !dvP[e.Name] { - errs = append(errs, e.ApplyDeviate(ms.ParseOptions.DeviateOptions)...) - dvP[e.Name] = true - } - } - } - - return errorSort(errs) -} - -// include resolves all the include and import statements for m. It returns -// an error if m, or recursively, any of the modules it includes or imports, -// reference a module that cannot be found. -func (ms *Modules) include(m *Module) error { - if ms.includes[m] { - return nil - } - ms.includes[m] = true - - // First process any includes in this module. - for _, i := range m.Include { - im := ms.FindModule(i) - if im == nil { - return fmt.Errorf("no such submodule: %s", i.Name) - } - // Process the include statements in our included module. - if err := ms.include(im); err != nil { - return err - } - i.Module = im - } - - // Next process any imports in this module. Imports are used - // when searching. - for _, i := range m.Import { - im := ms.FindModule(i) - if im == nil { - return fmt.Errorf("no such module: %s", i.Name) - } - // Process the include statements in our included module. - if err := ms.include(im); err != nil { - return err - } - - i.Module = im - } - return nil -} - -func (ms *Modules) getEntryCache(n Node) *Entry { - ms.entryCacheMu.RLock() - defer ms.entryCacheMu.RUnlock() - return ms.entryCache[n] -} - -func (ms *Modules) setEntryCache(n Node, e *Entry) { - ms.entryCacheMu.Lock() - defer ms.entryCacheMu.Unlock() - ms.entryCache[n] = e -} - -// ClearEntryCache clears the entryCache containing previously converted nodes -// used by the ToEntry function. -func (ms *Modules) ClearEntryCache() { - ms.entryCacheMu.Lock() - defer ms.entryCacheMu.Unlock() - ms.entryCache = map[Node]*Entry{} -} diff --git a/src/webui/internal/goyang/pkg/yang/modules_test.go b/src/webui/internal/goyang/pkg/yang/modules_test.go deleted file mode 100644 index b0a260c42..000000000 --- a/src/webui/internal/goyang/pkg/yang/modules_test.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2016 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "strings" - "testing" - - "github.com/openconfig/gnmi/errdiff" -) - -var testdataFindModulesText = map[string]string{ - "foo": `module foo { prefix "foo"; namespace "urn:foo"; }`, - "bar": `module bar { prefix "bar"; namespace "urn:bar"; }`, - "baz": `module baz { prefix "baz"; namespace "urn:baz"; }`, - "dup-pre-one": `module dup-pre-one { prefix duplicate; namespace urn:duplicate:one; }`, - "dup-pre-two": `module dup-pre-two { prefix duplicate; namespace urn:duplicate:two; }`, - "dup-ns-one": `module dup-ns-one { prefix ns-one; namespace urn:duplicate; }`, - "dup-ns-two": `module dup-ns-two { prefix ns-two; namespace urn:duplicate; }`, -} - -func TestDupModule(t *testing.T) { - tests := []struct { - desc string - inModules map[string]string - wantErr bool - }{{ - desc: "two modules with the same name", - inModules: map[string]string{ - "foo": `module foo { prefix "foo"; namespace "urn:foo"; }`, - "bar": `module foo { prefix "foo"; namespace "urn:foo"; }`, - }, - wantErr: true, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - var err error - for name, modtext := range tt.inModules { - if err = ms.Parse(modtext, name+".yang"); err != nil { - break - } - } - if gotErr := err != nil; gotErr != tt.wantErr { - t.Fatalf("wantErr: %v, got error: %v", tt.wantErr, err) - } - }) - } -} - -func testModulesForTestdataModulesText(t *testing.T) *Modules { - ms := NewModules() - for name, modtext := range testdataFindModulesText { - if err := ms.Parse(modtext, name+".yang"); err != nil { - t.Fatalf("error importing testdataFindModulesText[%q]: %v", name, err) - } - } - if errs := ms.Process(); errs != nil { - for _, err := range errs { - t.Errorf("error: %v", err) - } - t.Fatalf("fatal error(s) calling Process()") - } - return ms -} - -func testModulesFindByCommonHandler(t *testing.T, i int, got, want *Module, wantError string, err error) { - if err != nil { - if wantError != "" { - if !strings.Contains(err.Error(), wantError) { - t.Errorf("[%d] want error containing %q, got %q", - i, wantError, err.Error()) - } - } else { - t.Errorf("[%d] unexpected error: %v", i, err) - } - } else if wantError != "" { - t.Errorf("[%d] want error containing %q, got nil", i, wantError) - } else if want != got { - t.Errorf("[%d] want module %#v, got %#v", i, want, got) - } -} - -func TestModulesFindByNamespace(t *testing.T) { - ms := testModulesForTestdataModulesText(t) - - for i, tc := range []struct { - namespace string - want *Module - wantError string - }{ - { - namespace: "does-not-exist", - wantError: `"does-not-exist": no such namespace`, - }, - { - namespace: "urn:foo", - want: ms.Modules["foo"], - }, - { - namespace: "urn:bar", - want: ms.Modules["bar"], - }, - { - namespace: "urn:baz", - want: ms.Modules["baz"], - }, - { - namespace: "urn:duplicate", - wantError: "namespace urn:duplicate matches two or more modules (dup-ns-", - }, - } { - got, err := ms.FindModuleByNamespace(tc.namespace) - testModulesFindByCommonHandler(t, i, got, tc.want, tc.wantError, err) - } -} - -func TestModuleLinkage(t *testing.T) { - tests := []struct { - desc string - inMods map[string]string - wantErrSubstr string - }{{ - desc: "invalid import", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - import sys { prefix sys; } - - revision 01-01-01 { description "the start of time"; } - - deviation /sys:sys/sys:hostname { - deviate not-supported; - } - }`, - }, - wantErrSubstr: "no such module", - }, { - desc: "valid include", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - - revision 01-01-01 { description "the start of time"; } - - container sys { leaf hostname { type string; } } - }`, - }, - }, { - desc: "invalid include", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - }`, - "sysdb": ` - submodule sysdb { - belongs-to dev { - prefix "d"; - } - - revision 01-01-01 { description "the start of time"; } - - container sys { leaf hostname { type string; } } - }`, - }, - wantErrSubstr: "no such submodule", - }, { - desc: "valid include in submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - include sysdb; - - revision 01-01-01 { description "the start of time"; } - - container sys { leaf hostname { type string; } } - }`, - "sysdb": ` - submodule sysdb { - belongs-to dev { - prefix "d"; - } - - revision 01-01-01 { description "the start of time"; } - - container sysdb { leaf hostname { type string; } } - }`, - }, - }, { - desc: "invalid include in submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - include sysdb; - - revision 01-01-01 { description "the start of time"; } - - container sys { leaf hostname { type string; } } - }`, - "syyysdb": ` - submodule syyysdb { - belongs-to dev { - prefix "d"; - } - - revision 01-01-01 { description "the start of time"; } - - container sysdb { leaf hostname { type string; } } - }`, - }, - wantErrSubstr: "no such submodule", - }, { - desc: "valid import in submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - import sysdb { - prefix "sd"; - } - - revision 01-01-01 { description "the start of time"; } - - container sys { leaf hostname { type string; } } - }`, - "sysdb": ` - module sysdb { - prefix sd; - namespace "urn:sd"; - - revision 01-01-01 { description "the start of time"; } - - container sysdb { leaf hostname { type string; } } - }`, - }, - }, { - desc: "invalid import in submodule", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - include sys; - - revision 01-01-01 { description "the start of time"; } - }`, - "sys": ` - submodule sys { - belongs-to dev { - prefix "d"; - } - import sysdb { - prefix "sd"; - } - - revision 01-01-01 { description "the start of time"; } - - container sys { leaf hostname { type string; } } - }`, - "syyysdb": ` - module syyysdb { - prefix sd; - namespace "urn:sd"; - - revision 01-01-01 { description "the start of time"; } - - container sysdb { leaf hostname { type string; } } - }`, - }, - wantErrSubstr: "no such module", - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - - for n, m := range tt.inMods { - if err := ms.Parse(m, n); err != nil { - t.Fatalf("cannot parse module %s, err: %v", n, err) - } - } - - errs := ms.Process() - var err error - switch len(errs) { - case 1: - err = errs[0] - fallthrough - case 0: - if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Fatalf("%s", diff) - } - default: - t.Fatalf("got multiple errors: %v", errs) - } - }) - } -} - -func TestModulesTotalProcess(t *testing.T) { - tests := []struct { - desc string - inMods map[string]string - wantErr bool - }{{ - desc: "import with deviation", - inMods: map[string]string{ - "dev": ` - module dev { - prefix d; - namespace "urn:d"; - import sys { prefix sys; } - - revision 01-01-01 { description "the start of time"; } - - deviation /sys:sys/sys:hostname { - deviate not-supported; - } - }`, - "sys": ` - module sys { - prefix s; - namespace "urn:s"; - - revision 01-01-01 { description "the start of time"; } - - container sys { leaf hostname { type string; } } - }`, - }, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - - for n, m := range tt.inMods { - if err := ms.Parse(m, n); err != nil { - t.Fatalf("cannot parse module %s, err: %v", n, err) - } - } - - errs := ms.Process() - switch { - case len(errs) == 0 && tt.wantErr: - t.Fatalf("did not get expected errors, got: %v, wantErr: %v", errs, tt.wantErr) - case len(errs) != 0 && !tt.wantErr: - t.Fatalf("got unexpected errors, got: %v, wantErr: %v", errs, tt.wantErr) - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yang/node.go b/src/webui/internal/goyang/pkg/yang/node.go deleted file mode 100644 index ee52efeb8..000000000 --- a/src/webui/internal/goyang/pkg/yang/node.go +++ /dev/null @@ -1,388 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "errors" - "fmt" - "io" - "reflect" - "strings" - - "github.com/openconfig/goyang/pkg/indent" -) - -// A Node contains a yang statement and all attributes and sub-statements. -// Only pointers to structures should implement Node. -type Node interface { - // Kind returns the kind of yang statement (the keyword). - Kind() string - // NName returns the node's name (the argument) - NName() string - // Statement returns the original Statement of this Node. - Statement() *Statement - // ParentNode returns the parent of this Node, or nil if the - // Node has no parent. - ParentNode() Node - // Exts returns the list of extension statements found. - Exts() []*Statement -} - -// A Typedefer is a Node that defines typedefs. -type Typedefer interface { - Node - Typedefs() []*Typedef -} - -// An ErrorNode is a node that only contains an error. -type ErrorNode struct { - Parent Node `yang:"Parent,nomerge"` - - Error error -} - -func (ErrorNode) Kind() string { return "error" } -func (s *ErrorNode) ParentNode() Node { return s.Parent } -func (s *ErrorNode) NName() string { return "error" } -func (s *ErrorNode) Statement() *Statement { return &Statement{} } -func (s *ErrorNode) Exts() []*Statement { return nil } - -// isRPCNode is a terrible hack to return back that a path points into -// an RPC and we should ignore it. -var isRPCNode = &ErrorNode{Error: errors.New("rpc is unsupported")} - -// Source returns the location of the source where n was defined. -func Source(n Node) string { - if n != nil && n.Statement() != nil { - return n.Statement().Location() - } - return "unknown" -} - -// getPrefix returns the prefix and base name of s. If s has no prefix -// then the returned prefix is "". -func getPrefix(s string) (string, string) { - f := strings.SplitN(s, ":", 2) - if len(f) == 1 { - return "", s - } - return f[0], f[1] -} - -// Prefix notes for types: -// -// If there is prefix, look in nodes ancestors. -// -// If prefix matches the module's prefix statement, look in nodes ancestors. -// -// If prefix matches the submodule's belongs-t statement, look in nodes -// ancestors. -// -// Finally, look in the module imported with prefix. - -// FindModuleByPrefix finds the module or submodule with the provided prefix -// relative to where n was defined. If the prefix cannot be resolved then nil -// is returned. -func FindModuleByPrefix(n Node, prefix string) *Module { - if n == nil { - return nil - } - mod := RootNode(n) - - if prefix == "" || prefix == mod.GetPrefix() { - return mod - } - - for _, i := range mod.Import { - if prefix == i.Prefix.Name { - return mod.Modules.FindModule(i) - } - } - return nil -} - -// MatchingExtensions returns the subset of the given node's extensions -// that match the given module and identifier. -func MatchingExtensions(n Node, module, identifier string) ([]*Statement, error) { - return matchingExtensions(n, n.Exts(), module, identifier) -} - -// MatchingEntryExtensions returns the subset of the given entry's extensions -// that match the given module and identifier. -func MatchingEntryExtensions(e *Entry, module, identifier string) ([]*Statement, error) { - return matchingExtensions(e.Node, e.Exts, module, identifier) -} - -// matchingEntryExtensions returns the subset of the given node's extensions -// that match the given module and identifier. -func matchingExtensions(n Node, exts []*Statement, module, identifier string) ([]*Statement, error) { - var matchingExtensions []*Statement - for _, ext := range exts { - names := strings.SplitN(ext.Keyword, ":", 2) - mod := FindModuleByPrefix(n, names[0]) - if mod == nil { - return nil, fmt.Errorf("matchingExtensions: module prefix %q not found", names[0]) - } - if len(names) == 2 && names[1] == identifier && mod.Name == module { - matchingExtensions = append(matchingExtensions, ext) - } - } - return matchingExtensions, nil -} - -// RootNode returns the submodule or module that n was defined in. -func RootNode(n Node) *Module { - for ; n.ParentNode() != nil; n = n.ParentNode() { - } - if mod, ok := n.(*Module); ok { - return mod - } - return nil -} - -// module returns the Module to which n belongs. If n resides in a submodule, -// the belonging module will be returned. -// If n is nil or a module could not be find, nil is returned. -func module(n Node) *Module { - m := RootNode(n) - if m.Kind() == "submodule" { - m = m.Modules.Modules[m.BelongsTo.Name] - } - return m -} - -// NodePath returns the full path of the node from the module name. -func NodePath(n Node) string { - var path string - for n != nil { - path = "/" + n.NName() + path - n = n.ParentNode() - } - return path -} - -// FindNode finds the node referenced by path relative to n. If path does not -// reference a node then nil is returned (i.e. path not found). The path looks -// similar to an XPath but currently has no wildcarding. For example: -// "/if:interfaces/if:interface" and "../config". -func FindNode(n Node, path string) (Node, error) { - if path == "" { - return n, nil - } - // / is not a valid path, it needs a module name - if path == "/" { - return nil, fmt.Errorf("invalid path %q", path) - } - // Paths do not end in /'s - if path[len(path)-1] == '/' { - return nil, fmt.Errorf("invalid path %q", path) - } - - parts := strings.Split(path, "/") - - // An absolute path has a leading component of "". - // We need to discover which module they are part of - // based on our imports. - if parts[0] == "" { - parts = parts[1:] - - // TODO(borman): merge this with FindModuleByPrefix? - // The base is always a module - mod := RootNode(n) - n = mod - prefix, _ := getPrefix(parts[0]) - if mod.Kind() == "submodule" { - m := mod.Modules.Modules[mod.BelongsTo.Name] - if m == nil { - return nil, fmt.Errorf("%s: unknown module %s", m.Name, mod.BelongsTo.Name) - } - if prefix == "" || prefix == mod.BelongsTo.Prefix.Name { - goto processing - } - mod = m - } - - if prefix == "" || prefix == mod.Prefix.Name { - goto processing - } - - for _, i := range mod.Import { - if prefix == i.Prefix.Name { - n = i.Module - goto processing - } - } - // We didn't find a matching prefix. - return nil, fmt.Errorf("unknown prefix: %q", prefix) - processing: - // At this point, n should be pointing to the Module node - // of module we are rooted in - } - - for _, part := range parts { - // If we encounter an RPC node in our search then we - // return the magic isRPCNode Node which just contains - // an error that it is an RPC node. isRPCNode is a singleton - // and can be checked against. - if n.Kind() == "rpc" { - return isRPCNode, nil - } - if part == ".." { - Loop: - for { - n = n.ParentNode() - if n == nil { - return nil, fmt.Errorf(".. with no parent") - } - // choice, leaf, and case nodes - // are "invisible" when doing ".." - // up the tree. - switch n.Kind() { - case "choice", "leaf", "case": - default: - break Loop - } - } - continue - } - // For now just strip off any prefix - // TODO(borman): fix this - _, spart := getPrefix(part) - n = ChildNode(n, spart) - if n == nil { - return nil, fmt.Errorf("%s: no such element", part) - } - } - return n, nil -} - -// ChildNode finds n's child node named name. It returns nil if the node -// could not be found. ChildNode looks at every direct Node pointer in -// n as well as every node in all slices of Node pointers. Names must -// be non-ambiguous, otherwise ChildNode has a non-deterministic result. -func ChildNode(n Node, name string) Node { - v := reflect.ValueOf(n).Elem() - t := v.Type() - nf := t.NumField() - -Loop: - for i := 0; i < nf; i++ { - ft := t.Field(i) - yang := ft.Tag.Get("yang") - if yang == "" { - continue - } - parts := strings.Split(yang, ",") - for _, p := range parts[1:] { - if p == "nomerge" { - continue Loop - } - } - - f := v.Field(i) - if !f.IsValid() || f.IsNil() { - continue - } - - check := func(n Node) Node { - if n.NName() == name { - return n - } - return nil - } - if parts[0] == "uses" { - check = func(n Node) Node { - uname := n.NName() - // unrooted uses are rooted at root - if !strings.HasPrefix(uname, "/") { - uname = "/" + uname - } - if n, _ = FindNode(n, uname); n != nil { - return ChildNode(n, name) - } - return nil - } - } - - switch ft.Type.Kind() { - case reflect.Ptr: - if n = check(f.Interface().(Node)); n != nil { - return n - } - case reflect.Slice: - sl := f.Len() - for i := 0; i < sl; i++ { - n = f.Index(i).Interface().(Node) - if n = check(n); n != nil { - return n - } - } - } - } - return nil -} - -// PrintNode prints node n to w, recursively. -// TODO(borman): display more information -func PrintNode(w io.Writer, n Node) { - v := reflect.ValueOf(n).Elem() - t := v.Type() - nf := t.NumField() - fmt.Fprintf(w, "%s [%s]\n", n.NName(), n.Kind()) -Loop: - for i := 0; i < nf; i++ { - ft := t.Field(i) - yang := ft.Tag.Get("yang") - if yang == "" { - continue - } - parts := strings.Split(yang, ",") - for _, p := range parts[1:] { - if p == "nomerge" { - continue Loop - } - } - - // Skip uppercase elements. - if parts[0][0] >= 'A' && parts[0][0] <= 'Z' { - continue - } - - f := v.Field(i) - if !f.IsValid() || f.IsNil() { - continue - } - - switch ft.Type.Kind() { - case reflect.Ptr: - n = f.Interface().(Node) - if v, ok := n.(*Value); ok { - fmt.Fprintf(w, "%s = %s\n", ft.Name, v.Name) - } else { - PrintNode(indent.NewWriter(w, " "), n) - } - case reflect.Slice: - sl := f.Len() - for i := 0; i < sl; i++ { - n = f.Index(i).Interface().(Node) - if v, ok := n.(*Value); ok { - fmt.Fprintf(w, "%s[%d] = %s\n", ft.Name, i, v.Name) - } else { - PrintNode(indent.NewWriter(w, " "), n) - } - } - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/node_test.go b/src/webui/internal/goyang/pkg/yang/node_test.go deleted file mode 100644 index 5fdac6283..000000000 --- a/src/webui/internal/goyang/pkg/yang/node_test.go +++ /dev/null @@ -1,622 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "errors" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/openconfig/gnmi/errdiff" -) - -func TestNodePath(t *testing.T) { - tests := []struct { - desc string - in Node - want string - }{{ - desc: "basic", - in: &Leaf{ - Name: "bar", - Parent: &Container{ - Name: "c", - Parent: &List{ - Name: "b", - Parent: &Module{ - Name: "foo", - }, - }, - }, - }, - want: "/foo/b/c/bar", - }, { - desc: "nil input node", - in: nil, - want: "", - }, { - desc: "single node", - in: &Module{ - Name: "foo", - }, - want: "/foo", - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - if diff := cmp.Diff(NodePath(tt.in), tt.want); diff != "" { - t.Errorf("(-got, +want):\n%s", diff) - } - }) - } -} - -// TestNode provides a framework for processing tests that can check particular -// nodes being added to the grammar. It can be used to ensure that particular -// statement combinations are supported, especially where they are opaque to -// the YANG library. -func TestNode(t *testing.T) { - tests := []struct { - desc string - inFn func(*Modules) (Node, error) - inModules map[string]string - wantNode func(Node) error - wantErrSubstr string - }{{ - desc: "import reference statement", - inFn: func(ms *Modules) (Node, error) { - - const module = "test" - m, ok := ms.Modules[module] - if !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - if len(m.Import) == 0 { - return nil, fmt.Errorf("node %v is missing imports", m) - } - - return m.Import[0], nil - }, - inModules: map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - import foo { - prefix "f"; - reference "bar"; - } - } - `, - "foo": ` - module foo { - prefix "f"; - namespace "urn:f"; - } - `, - }, - wantNode: func(n Node) error { - is, ok := n.(*Import) - if !ok { - return fmt.Errorf("got node: %v, want type: import", n) - } - - switch { - case is.Reference == nil: - return errors.New("did not get expected reference, got: nil, want: *yang.Statement") - case is.Reference.Statement().Argument != "bar": - return fmt.Errorf("did not get expected reference, got: %v, want: 'bar'", is.Reference.Statement()) - } - - return nil - }, - }, { - desc: "get submodule from prefix in submodule", - inFn: func(ms *Modules) (Node, error) { - - m, ok := ms.SubModules["foo"] - if !ok { - return nil, fmt.Errorf("can't find submodule in %v", ms) - } - - if m.BelongsTo == nil { - return nil, fmt.Errorf("node %v is missing belongs-to", m) - } - - return m.BelongsTo, nil - }, - inModules: map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - include foo { - revision-date 2008-01-01; - } - } - `, - "foo": ` - submodule foo { - belongs-to test { - prefix "t"; - } - } - `, - }, - wantNode: func(n Node) error { - is, ok := n.(*BelongsTo) - if !ok { - return fmt.Errorf("got node: %v, want type: belongs-to", n) - } - - switch { - case is.Prefix == nil: - return errors.New("did not get expected reference, got: nil, want: *yang.Statement") - case is.Prefix.Statement().Argument != "t": - return fmt.Errorf("did not get expected reference, got: %v, want: 't'", is.Prefix.Statement()) - } - - m := FindModuleByPrefix(is, is.Prefix.Statement().Argument) - if m == nil { - return fmt.Errorf("can't find module from submodule's belongs-to prefix value") - } - if want := "foo"; m.Name != want { - return fmt.Errorf("module from submodule's belongs-to prefix value doesn't match, got %q, want %q", m.Name, want) - } - - return nil - }, - }, { - desc: "import statement from submodule", - inFn: func(ms *Modules) (Node, error) { - - m, ok := ms.SubModules["foo"] - if !ok { - return nil, fmt.Errorf("can't find submodule in %v", ms) - } - - if len(m.Import) == 0 { - return nil, fmt.Errorf("node %v is missing import statement", m) - } - - return m.Import[0], nil - }, - inModules: map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - include foo { - revision-date 2008-01-01; - } - - typedef t { - type string; - } - } - `, - "foo": ` - submodule foo { - belongs-to test { - prefix "t"; - } - - import test2 { - prefix "t2"; - description "test2 module"; - } - } - `, - "test2": ` - module test2 { - prefix "t2"; - namespace "urn:t2"; - } - `, - }, - wantNode: func(n Node) error { - is, ok := n.(*Import) - if !ok { - return fmt.Errorf("got node: %v, want type: belongs-to", n) - } - - switch { - case is.Prefix == nil: - return errors.New("did not get expected reference, got: nil, want: *yang.Statement") - case is.Prefix.Statement().Argument != "t2": - return fmt.Errorf("did not get expected reference, got: %v, want: 't'", is.Prefix.Statement()) - } - - m := FindModuleByPrefix(is, is.Prefix.Statement().Argument) - if m == nil { - return fmt.Errorf("can't find module from submodule's import prefix value") - } - if want := "test2"; m.Name != want { - return fmt.Errorf("module from submodule's import prefix value doesn't match, got %q, want %q", m.Name, want) - } - - return nil - }, - }, { - desc: "import description statement", - inFn: func(ms *Modules) (Node, error) { - - const module = "test" - m, ok := ms.Modules[module] - if !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - if len(m.Import) == 0 { - return nil, fmt.Errorf("node %v is missing imports", m) - } - - return m.Import[0], nil - }, - inModules: map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - import foo { - prefix "f"; - description "foo module"; - } - } - `, - "foo": ` - module foo { - prefix "f"; - namespace "urn:f"; - } - `, - }, - wantNode: func(n Node) error { - is, ok := n.(*Import) - if !ok { - return fmt.Errorf("got node: %v, want type: import", n) - } - - switch { - case is.Description == nil: - return errors.New("did not get expected reference, got: nil, want: *yang.Statement") - case is.Description.Statement().Argument != "foo module": - return fmt.Errorf("did not get expected reference, got: '%v', want: 'foo module'", is.Description.Statement().Argument) - } - - return nil - }, - }, { - desc: "Test matchingExtensions", - inFn: func(ms *Modules) (Node, error) { - - module := "test" - m, ok := ms.Modules[module] - if !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - if len(m.Leaf) == 0 { - return nil, fmt.Errorf("node %v is missing imports", m) - } - - module = "foo" - if _, ok := ms.Modules[module]; !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - return m.Leaf[0].Type, nil - }, - inModules: map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - import foo { - prefix "f"; - description "foo module"; - } - - import foo2 { - prefix "f2"; - description "foo2 module"; - } - - leaf test-leaf { - type string { - pattern 'alpha'; - // Test different modules and different ext names. - f:bar 'boo'; - f2:bar 'boo2'; - - f:bar 'coo'; - f2:bar 'coo2'; - - f:far 'doo'; - f2:far 'doo2'; - - f:bar 'foo'; - f2:bar 'foo2'; - - f:far 'goo'; - f2:far 'goo2'; - } - } - } - `, - "foo": ` - module foo { - prefix "f"; - namespace "urn:f"; - - extension bar { - argument "baz"; - } - - extension far { - argument "baz"; - } - } - `, - "foo2": ` - module foo2 { - prefix "f2"; - namespace "urn:f2"; - - extension bar { - argument "baz"; - } - - extension far { - argument "baz"; - } - } - `, - }, - wantNode: func(n Node) error { - n, ok := n.(*Type) - if !ok { - return fmt.Errorf("got node: %v, want type: Leaf", n) - } - - var bars []string - matches, err := matchingExtensions(n, n.Exts(), "foo", "bar") - if err != nil { - return err - } - for _, ext := range matches { - bars = append(bars, ext.Argument) - } - - if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" { - return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff) - } - - return nil - }, - }, { - desc: "Test matchingExtensions when module is not found", - inFn: func(ms *Modules) (Node, error) { - - module := "test" - m, ok := ms.Modules[module] - if !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - if len(m.Leaf) == 0 { - return nil, fmt.Errorf("node %v is missing imports", m) - } - - module = "foo" - if _, ok := ms.Modules[module]; !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - return m.Leaf[0].Type, nil - }, - inModules: map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - import foo { - prefix "f"; - description "foo module"; - } - - leaf test-leaf { - type string { - pattern 'alpha'; - not-found:bar 'foo'; - } - } - } - `, - "foo": ` - module foo { - prefix "f"; - namespace "urn:f"; - - extension bar { - argument "baz"; - } - - extension far { - argument "baz"; - } - } - `, - }, - wantNode: func(n Node) error { - n, ok := n.(*Type) - if !ok { - return fmt.Errorf("got node: %v, want type: Leaf", n) - } - - var bars []string - matches, err := matchingExtensions(n, n.Exts(), "foo", "bar") - if err != nil { - return err - } - for _, ext := range matches { - bars = append(bars, ext.Argument) - } - - if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" { - return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff) - } - - return nil - }, - wantErrSubstr: `module prefix "not-found" not found`, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - - for n, m := range tt.inModules { - if err := ms.Parse(m, n); err != nil { - t.Errorf("error parsing module %s, got: %v, want: nil", n, err) - } - } - - errs := ms.Process() - var err error - if len(errs) > 1 { - t.Fatalf("Got more than 1 error: %v", errs) - } else if len(errs) == 1 { - err = errs[0] - } - if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Errorf("Did not get expected error: %s", diff) - } - if err != nil { - return - } - - node, err := tt.inFn(ms) - if err != nil { - t.Fatalf("cannot run in function, %v", err) - } - - if err := tt.wantNode(node); err != nil { - t.Fatalf("failed check function, %v", err) - } - }) - } -} - -func TestModulesFindByPrefix(t *testing.T) { - // Some examples of where prefixes might be used are in the following - // YANG statements: extension, uses, augment, deviation, type, leafref. - // Not all are put into the test here, since the logic is the same for - // each. - modules := map[string]string{ - "foo": `module foo { prefix "foo"; namespace "urn:foo"; include bar; leaf leafref { type leafref { path "../foo:leaf"; } } uses foo:lg; }`, - "bar": `submodule bar { belongs-to foo { prefix "bar"; } container c { uses bar:lg; } grouping lg { leaf leaf { type string; } } }`, - "baz": `module baz { prefix "foo"; namespace "urn:foo"; import foo { prefix f; } extension e; uses f:lg; foo:e; }`, - } - - ms := NewModules() - for name, modtext := range modules { - if err := ms.Parse(modtext, name+".yang"); err != nil { - t.Fatalf("error parsing module %q: %v", name, err) - } - } - if errs := ms.Process(); errs != nil { - for _, err := range errs { - t.Errorf("error: %v", err) - } - t.Fatalf("fatal error(s) calling Process()") - } - - for _, tt := range []struct { - desc string - node Node - prefix string - want *Module - }{ - { - desc: "nil node", - node: nil, - prefix: "does-not-exist", - want: nil, - }, - { - desc: "module foo", - node: ms.Modules["foo"], - prefix: "foo", - want: ms.Modules["foo"], - }, - { - desc: "submodule bar", - node: ms.SubModules["bar"], - prefix: "bar", - want: ms.SubModules["bar"], - }, - { - desc: "module baz", - node: ms.Modules["baz"], - prefix: "foo", - want: ms.Modules["baz"], - }, - { - desc: "foo leafref", - node: ms.Modules["foo"].Leaf[0].Type, - prefix: "foo", - want: ms.Modules["foo"], - }, - { - desc: "foo uses", - node: ms.Modules["foo"].Uses[0], - prefix: "foo", - want: ms.Modules["foo"], - }, - { - desc: "bar uses", - node: ms.SubModules["bar"].Container[0].Uses[0], - prefix: "bar", - want: ms.SubModules["bar"], - }, - { - desc: "baz uses", - node: ms.Modules["baz"].Uses[0], - prefix: "f", - want: ms.Modules["foo"], - }, - { - desc: "baz extension", - node: ms.Modules["baz"], - prefix: "foo", - want: ms.Modules["baz"], - }, - } { - t.Run(tt.desc, func(t *testing.T) { - if got := FindModuleByPrefix(tt.node, tt.prefix); got != tt.want { - t.Errorf("got: %v, want: %v", got, tt.want) - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yang/options.go b/src/webui/internal/goyang/pkg/yang/options.go deleted file mode 100644 index 2de2ebd57..000000000 --- a/src/webui/internal/goyang/pkg/yang/options.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// Options defines the options that should be used when parsing YANG modules, -// including specific overrides for potentially problematic YANG constructs. -type Options struct { - // IgnoreSubmoduleCircularDependencies specifies whether circular dependencies - // between submodules. Setting this value to true will ensure that this - // package will explicitly ignore the case where a submodule will include - // itself through a circular reference. - IgnoreSubmoduleCircularDependencies bool - // StoreUses controls whether the Uses field of each YANG entry should be - // populated. Setting this value to true will cause each Entry which is - // generated within the schema to store the logical grouping from which it - // is derived. - StoreUses bool - // DeviateOptions contains options for how deviations are handled. - DeviateOptions DeviateOptions -} - -// DeviateOptions contains options for how deviations are handled. -type DeviateOptions struct { - // IgnoreDeviateNotSupported indicates to the parser to retain nodes - // that are marked with "deviate not-supported". An example use case is - // where the user wants to interact with different targets that have - // different support for a leaf without having to use a second instance - // of an AST. - IgnoreDeviateNotSupported bool -} - -// IsDeviateOpt ensures that DeviateOptions satisfies the DeviateOpt interface. -func (DeviateOptions) IsDeviateOpt() {} - -// DeviateOpt is an interface that can be used in function arguments. -type DeviateOpt interface { - IsDeviateOpt() -} - -func hasIgnoreDeviateNotSupported(opts []DeviateOpt) bool { - for _, o := range opts { - if opt, ok := o.(DeviateOptions); ok { - return opt.IgnoreDeviateNotSupported - } - } - return false -} diff --git a/src/webui/internal/goyang/pkg/yang/parse.go b/src/webui/internal/goyang/pkg/yang/parse.go deleted file mode 100644 index 60c388b68..000000000 --- a/src/webui/internal/goyang/pkg/yang/parse.go +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// This file implements Parse, which parses the input as generic YANG and -// returns a slice of base Statements (which in turn may contain more -// Statements, i.e., a slice of Statement trees.) - -import ( - "bytes" - "errors" - "fmt" - "io" - "strings" -) - -// a parser is used to parse the contents of a single .yang file. -type parser struct { - lex *lexer - errout *bytes.Buffer - tokens []*token // stack of pushed tokens (for backing up) - - // Depth of statements in nested braces - statementDepth int - - // hitBrace is returned when we encounter a '}'. The statement location - // is updated with the location of the '}'. The brace may be legitimate - // but only the caller will know if it is. That is, the brace may be - // closing our parent or may be an error (we didn't expect it). - // hitBrace is updated with the file, line, and column of the brace's - // location. - hitBrace *Statement -} - -// Statement is a generic YANG statement that may have sub-statements. -// It implements the Node interface. -// -// Within the parser, it represents a non-terminal token. -// From https://tools.ietf.org/html/rfc7950#section-6.3: -// statement = keyword [argument] (";" / "{" *statement "}") -// The argument is a string. -type Statement struct { - Keyword string - HasArgument bool - Argument string - statements []*Statement - - file string - line int // 1's based line number - col int // 1's based column number -} - -func (s *Statement) NName() string { return s.Argument } -func (s *Statement) Kind() string { return s.Keyword } -func (s *Statement) Statement() *Statement { return s } -func (s *Statement) ParentNode() Node { return nil } -func (s *Statement) Exts() []*Statement { return nil } - -// Arg returns the optional argument to s. It returns false if s has no -// argument. -func (s *Statement) Arg() (string, bool) { return s.Argument, s.HasArgument } - -// SubStatements returns a slice of Statements found in s. -func (s *Statement) SubStatements() []*Statement { return s.statements } - -// Location returns the location in the source where s was defined. -func (s *Statement) Location() string { - switch { - case s.file == "" && s.line == 0: - return "unknown" - case s.file == "": - return fmt.Sprintf("line %d:%d", s.line, s.col) - case s.line == 0: - return s.file - default: - return fmt.Sprintf("%s:%d:%d", s.file, s.line, s.col) - } -} - -// Write writes the tree in s to w, each line indented by ident. Children -// nodes are indented further by a tab. Typically indent is "" at the top -// level. Write is intended to display the contents of Statement, but -// not necessarily reproduce the input of Statement. -func (s *Statement) Write(w io.Writer, indent string) error { - if s.Keyword == "" { - // We are just a collection of statements at the top level. - for _, s := range s.statements { - if err := s.Write(w, indent); err != nil { - return err - } - } - return nil - } - - parts := []string{fmt.Sprintf("%s%s", indent, s.Keyword)} - if s.HasArgument { - args := strings.Split(s.Argument, "\n") - if len(args) == 1 { - parts = append(parts, fmt.Sprintf(" %q", s.Argument)) - } else { - parts = append(parts, ` "`, args[0], "\n") - i := fmt.Sprintf("%*s", len(s.Keyword)+1, "") - for x, p := range args[1:] { - s := fmt.Sprintf("%q", p) - s = s[1 : len(s)-1] - parts = append(parts, indent, " ", i, s) - if x == len(args[1:])-1 { - // last part just needs the closing " - parts = append(parts, `"`) - } else { - parts = append(parts, "\n") - } - } - } - } - - if len(s.statements) == 0 { - _, err := fmt.Fprintf(w, "%s;\n", strings.Join(parts, "")) - return err - } - if _, err := fmt.Fprintf(w, "%s {\n", strings.Join(parts, "")); err != nil { - return err - } - for _, s := range s.statements { - if err := s.Write(w, indent+"\t"); err != nil { - return err - } - } - if _, err := fmt.Fprintf(w, "%s}\n", indent); err != nil { - return err - } - return nil -} - -// ignoreMe is an error recovery token used by the parser in order -// to continue processing for other errors in the file. -var ignoreMe = &Statement{} - -// Parse parses the input as generic YANG and returns the statements parsed. -// The path parameter should be the source name where input was read from (e.g., -// the file name the input was read from). If one more more errors are -// encountered, nil and an error are returned. The error's text includes all -// errors encountered. -func Parse(input, path string) ([]*Statement, error) { - var statements []*Statement - p := &parser{ - lex: newLexer(input, path), - errout: &bytes.Buffer{}, - hitBrace: &Statement{}, - } - p.lex.errout = p.errout -Loop: - for { - switch ns := p.nextStatement(); ns { - case nil: - break Loop - case p.hitBrace: - fmt.Fprintf(p.errout, "%s:%d:%d: unexpected %c\n", ns.file, ns.line, ns.col, '}') - default: - statements = append(statements, ns) - } - } - - p.checkStatementDepthIsZero() - - if p.errout.Len() == 0 { - return statements, nil - } - return nil, errors.New(strings.TrimSpace(p.errout.String())) -} - -// push pushes tokens t back on the input stream so they will be the next -// tokens returned by next. The tokens list is a LIFO so the final token -// listed to push will be the next token returned. -func (p *parser) push(t ...*token) { - p.tokens = append(p.tokens, t...) -} - -// pop returns the last token pushed, or nil if the token stack is empty. -func (p *parser) pop() *token { - if n := len(p.tokens); n > 0 { - n-- - defer func() { p.tokens = p.tokens[:n] }() - return p.tokens[n] - } - return nil -} - -// next returns the next token from the lexer. If the next token is a -// concatenated string, it returns the concatenated string as the token. -func (p *parser) next() *token { - if t := p.pop(); t != nil { - return t - } - // next returns the next unprocessed lexer token. - next := func() *token { - for { - if t := p.lex.NextToken(); t.Code() != tError { - return t - } - } - } - t := next() - if t.Code() != tString { - return t - } - // Process string concatenation (both single and double quote). - // See https://tools.ietf.org/html/rfc7950#section-6.1.3.1 - // The lexer trimmed the quotes already. - for { - nt := next() - switch nt.Code() { - case tEOF: - return t - case tUnquoted: - if nt.Text != "+" { - p.push(nt) - return t - } - default: - p.push(nt) - return t - } - // Invariant: nt is a + sign. - nnt := next() - switch nnt.Code() { - case tEOF: - p.push(nt) - return t - case tString: - // Accumulate the concatenation. - t.Text += nnt.Text - default: - p.push(nnt, nt) - return t - } - } -} - -// nextStatement returns the next statement in the input, which may in turn -// recurse to read sub statements. -// nil is returned when EOF has been reached, or is reached halfway through -// parsing the next statement (with associated syntax errors printed to -// errout). -func (p *parser) nextStatement() *Statement { - t := p.next() - switch t.Code() { - case tEOF: - return nil - case '}': - p.statementDepth -= 1 - p.hitBrace.file = t.File - p.hitBrace.line = t.Line - p.hitBrace.col = t.Col - return p.hitBrace - case tUnquoted: - default: - fmt.Fprintf(p.errout, "%v: keyword token not an unquoted string\n", t) - return ignoreMe - } - // Invariant: t represents a keyword token. - - s := &Statement{ - Keyword: t.Text, - file: t.File, - line: t.Line, - col: t.Col, - } - - // The keyword "pattern" must be treated specially. When - // parsing the argument for "pattern", escape sequences - // must be expanded differently. - p.lex.inPattern = t.Text == "pattern" - t = p.next() - p.lex.inPattern = false - switch t.Code() { - case tString, tUnquoted: - s.HasArgument = true - s.Argument = t.Text - t = p.next() - } - - switch t.Code() { - case tEOF: - fmt.Fprintf(p.errout, "%s: unexpected EOF\n", s.file) - return nil - case ';': - return s - case '{': - p.statementDepth += 1 - for { - switch ns := p.nextStatement(); ns { - case nil: - // Signal EOF reached. - return nil - case p.hitBrace: - return s - default: - s.statements = append(s.statements, ns) - } - } - default: - fmt.Fprintf(p.errout, "%v: syntax error, expected ';' or '{'\n", t) - return ignoreMe - } -} - -// checkStatementDepthIsZero checks that we aren't missing closing -// braces. Note: the parser will error out for the case where we -// start with an unmatched close brace, i.e. depth < 0 -// -// This test should only be done if there are no other errors as -// we may exit early due to those errors -- and therefore there *might* -// not really be a mismatched brace issue. -func (p *parser) checkStatementDepthIsZero() { - if p.errout.Len() > 0 || p.statementDepth == 0 { - return - } - - plural := "" - if p.statementDepth > 1 { - plural = "s" - } - fmt.Fprintf(p.errout, "%s:%d:%d: missing %d closing brace%s\n", - p.lex.file, p.lex.line, p.lex.col, p.statementDepth, plural) -} diff --git a/src/webui/internal/goyang/pkg/yang/parse_test.go b/src/webui/internal/goyang/pkg/yang/parse_test.go deleted file mode 100644 index 33269966d..000000000 --- a/src/webui/internal/goyang/pkg/yang/parse_test.go +++ /dev/null @@ -1,539 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "bytes" - "testing" -) - -func (s1 *Statement) equal(s2 *Statement) bool { - if s1.Keyword != s2.Keyword || - s1.HasArgument != s2.HasArgument || - s1.Argument != s2.Argument || - len(s1.statements) != len(s2.statements) { - return false - } - - for x, ss := range s1.statements { - if !ss.equal(s2.statements[x]) { - return false - } - } - return true -} - -// SA returns a statement with an argument and optional substatements. -func SA(k, a string, ss ...*Statement) *Statement { - return &Statement{ - Keyword: k, - Argument: a, - HasArgument: true, - statements: ss, - } -} - -// S returns a statement with no argument and optional substatements. -func S(k string, ss ...*Statement) *Statement { - return &Statement{ - Keyword: k, - statements: ss, - } -} - -func TestParse(t *testing.T) { - for _, tt := range []struct { - line int - in string - out []*Statement - err string - }{ - {line: line()}, - {line: line(), in: ` -foo; -`, - out: []*Statement{ - S("foo"), - }, - }, - {line: line(), in: ` -foo {} -`, - out: []*Statement{ - S("foo"), - }, - }, - {line: line(), in: ` -foo ""; -`, - out: []*Statement{ - SA("foo", ""), - }, - }, - {line: line(), in: ` -foo bar; -`, - out: []*Statement{ - SA("foo", "bar"), - }, - }, - {line: line(), in: ` -foo "bar"; -`, - out: []*Statement{ - SA("foo", "bar"), - }, - }, - {line: line(), in: ` -foo "\\ \S \n"; -`, - err: `test.yang:2:9: invalid escape sequence: \S`, - }, - {line: line(), in: ` -pattern "\\ \S \n"; -`, - out: []*Statement{ - SA("pattern", `\ \S -`), - }, - }, - {line: line(), in: ` -foo '\\ \S \n'; -`, - out: []*Statement{ - SA("foo", `\\ \S \n`), - }, - }, - {line: line(), in: ` -pattern '\\ \S \n'; -`, - out: []*Statement{ - SA("pattern", `\\ \S \n`), - }, - }, - {line: line(), in: ` -foo "bar" + "baz"; -`, - out: []*Statement{ - SA("foo", "barbaz"), - }, - }, - {line: line(), in: ` -foo "bar" + "+" + "baz"; -`, - out: []*Statement{ - SA("foo", "bar+baz"), - }, - }, - {line: line(), in: ` -foo "bar" -`, - err: `test.yang: unexpected EOF`, - }, - {line: line(), in: ` -foo "bar" + "baz" -`, - err: `test.yang: unexpected EOF`, - }, - {line: line(), in: ` -foo "bar" baz; -`, - err: `test.yang:2:11: baz: syntax error, expected ';' or '{' -test.yang:2:14: ;: keyword token not an unquoted string`, - }, - {line: line(), in: ` -foo "bar" + baz; -`, - err: `test.yang:2:11: +: syntax error, expected ';' or '{'`, - }, - {line: line(), in: ` -foo "bar" + -`, - err: `test.yang:2:11: +: syntax error, expected ';' or '{'`, - }, - {line: line(), in: ` -foo "bar"; -`, - out: []*Statement{ - SA("foo", "bar"), - }, - }, - {line: line(), in: ` -foo "bar" {} -`, - out: []*Statement{ - SA("foo", "bar"), - }, - }, - {line: line(), in: ` -foo 'bar' + 'baz'; -`, - out: []*Statement{ - SA("foo", "barbaz"), - }, - }, - {line: line(), in: ` -foo 'bar' + '+' + 'baz'; -`, - out: []*Statement{ - SA("foo", "bar+baz"), - }, - }, - {line: line(), in: ` -foo 'bar' -`, - err: `test.yang: unexpected EOF`, - }, - {line: line(), in: ` -foo 'bar' + 'baz' -`, - err: `test.yang: unexpected EOF`, - }, - {line: line(), in: ` -foo 'bar' baz; -`, - err: `test.yang:2:11: baz: syntax error, expected ';' or '{' -test.yang:2:14: ;: keyword token not an unquoted string`, - }, - {line: line(), in: ` -foo 'bar' + baz; -`, - err: `test.yang:2:11: +: syntax error, expected ';' or '{'`, - }, - {line: line(), in: ` -foo 'bar' + -`, - err: `test.yang:2:11: +: syntax error, expected ';' or '{'`, - }, - {line: line(), in: ` -foo 'bar'; -`, - out: []*Statement{ - SA("foo", "bar"), - }, - }, - {line: line(), in: ` -foo 'bar' {} -`, - out: []*Statement{ - SA("foo", "bar"), - }, - }, - {line: line(), in: ` -foo bar; -red black; -`, - out: []*Statement{ - SA("foo", "bar"), - SA("red", "black"), - }, - }, - {line: line(), in: ` -foo { - key value; -} -`, - out: []*Statement{ - S("foo", - SA("key", "value"), - ), - }, - }, - {line: line(), in: ` -foo { - key value; -} -`, - out: []*Statement{ - S("foo", - SA("key", "value"), - ), - }, - }, - {line: line(), in: ` -foo { - key "value1 value2 - - value3"; -} -`, - out: []*Statement{ - S("foo", - SA("key", "value1 value2\n\n value3"), - ), - }, - }, - {line: line(), in: ` -foo { - key value; - key2; -} -`, - out: []*Statement{ - S("foo", - SA("key", "value"), - S("key2"), - ), - }, - }, - {line: line(), in: ` -foo1 { - key value1; -} -foo2 { - key value2; -} -foo3 value3; -`, - out: []*Statement{ - S("foo1", - SA("key", "value1"), - ), - S("foo2", - SA("key", "value2"), - ), - SA("foo3", "value3"), - }, - }, - {line: line(), in: ` -foo1 { - key value1; - foo2 { - key value2; - } -} -`, - out: []*Statement{ - S("foo1", - SA("key", "value1"), - S("foo2", - SA("key", "value2"), - ), - ), - }, - }, - {line: line(), in: ` -foo1 { - key value1; - foo2 { - pattern '[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+' - + '(\.[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+)*' - + '@' - + '[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+' - + '(\.[a-zA-Z0-9!#$%&'+"'"+'*+/=?^_` + "`" + `{|}~-]+)*'; - } -} -`, - out: []*Statement{ - S("foo1", - SA("key", "value1"), - S("foo2", - SA("pattern", "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*"), - ), - ), - }, - }, - {line: line(), in: ` - } -`, - err: `test.yang:2:2: unexpected }`, - }, - {line: line(), in: ` -id -`, - err: `test.yang: unexpected EOF`, - }, - {line: line(), in: ` - { -`, - err: `test.yang:2:4: {: keyword token not an unquoted string`, - }, - {line: line(), in: ` -; -`, - err: `test.yang:2:1: ;: keyword token not an unquoted string`, - }, - {line: line(), in: ` -statement one two { } -`, - err: `test.yang:2:15: two: syntax error, expected ';' or '{' -test.yang:2:19: {: keyword token not an unquoted string -test.yang:2:21: unexpected }`, - }, - {line: line(), in: ` - } -foo { - key: "value"; -} -`, - err: `test.yang:2:5: unexpected }`, - }, - {line: line(), in: ` -{ - something: "bad"; -} -foo { - key: "\Value"; - key2: "value2"; - bar { - key3: "value\3; - } -}`, - err: `test.yang:2:1: {: keyword token not an unquoted string -test.yang:4:1: unexpected } -test.yang:6:8: invalid escape sequence: \V -test.yang:9:15: invalid escape sequence: \3 -test.yang:9:9: missing closing " -test.yang: unexpected EOF`, - }, - {line: line(), in: ` -module base { - container top-missing-close-brace { - leaf my-leaf { - type string; - } - } -`, - err: "test.yang:8:0: missing 1 closing brace", - }, - {line: line(), in: ` -module base { - container top-missing-close-brace { - leaf my-leaf { - type string; - } -`, - err: "test.yang:7:0: missing 2 closing braces", - }, - } { - s, err := Parse(tt.in, "test.yang") - if (s == nil) != (tt.out == nil) { - if s == nil { - t.Errorf("%d: did not get expected statements: %v", tt.line, tt.out) - } else { - t.Errorf("%d: get unexpected statements: %v", tt.line, s) - } - } - switch { - case err == nil && tt.err == "": - case tt.err == "": - t.Errorf("%d: unexpected error %v", tt.line, err) - continue - case err == nil: - t.Errorf("%d: did not get expected error %v", tt.line, tt.err) - continue - case err.Error() == tt.err: - continue - default: - t.Errorf("%d: got error:\n%s\nwant:\n%s", tt.line, err, tt.err) - continue - } - s1 := &Statement{statements: s} - s2 := &Statement{statements: tt.out} - if !s1.equal(s2) { - t.Errorf("%d: got:\n%v\nwant:\n%v", tt.line, s1, s2) - } - } -} - -func TestWrite(t *testing.T) { -Testing: - for _, tt := range []struct { - line int - in string - out string - }{ - {line: line(), - in: `key arg { substatement; }`, - out: `key "arg" { - substatement; -} -`, - }, - {line: line(), - in: `key { substatement { key arg; }}`, - out: `key { - substatement { - key "arg"; - } -} -`, - }, - {line: line(), - in: ` -module base { - namespace "urn:mod"; - prefix "base"; - - typedef base-type { type int32; } - - grouping base-group { - description - "The base-group is used to test the - 'uses' statement below. This description - is here to simply include a multi-line - string as an example of multi-line strings"; - leaf base-group-leaf { - config false; - type string; - } - } - uses base-group; -} -`, out: `module "base" { - namespace "urn:mod"; - prefix "base"; - typedef "base-type" { - type "int32"; - } - grouping "base-group" { - description "The base-group is used to test the - 'uses' statement below. This description - is here to simply include a multi-line - string as an example of multi-line strings"; - leaf "base-group-leaf" { - config "false"; - type "string"; - } - } - uses "base-group"; -} -`, - }, - } { - in := tt.in - // Run twice. The first time we are parsing tt.in, the second - // time we are parsing the output from the first parsing. - for i := 0; i < 2; i++ { - s, err := Parse(in, "test.yang") - if err != nil { - t.Errorf("%d: unexpected error %v", tt.line, err) - continue Testing - } - if len(s) != 1 { - t.Errorf("%d: got %d statements, expected 1", tt.line, len(s)) - continue Testing - } - var buf bytes.Buffer - s[0].Write(&buf, "") - out := buf.String() - if out != tt.out { - t.Errorf("%d: got:\n%swant:\n%s", tt.line, out, tt.out) - continue Testing - } - in = out - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/testdata/deviate-delete.yang b/src/webui/internal/goyang/pkg/yang/testdata/deviate-delete.yang deleted file mode 100644 index 8e8646665..000000000 --- a/src/webui/internal/goyang/pkg/yang/testdata/deviate-delete.yang +++ /dev/null @@ -1,89 +0,0 @@ -module deviate { - prefix "d"; - namespace "urn:d"; - - grouping substmts { - leaf config { - type string; - config true; - } - leaf default { - type string; - default "fish"; - } - leaf mandatory { - type string; - mandatory false; - } - leaf-list max-elements { - type string; - max-elements 1000; - } - leaf-list min-elements { - type string; - min-elements 1000; - } - leaf-list max-and-min-elements { - type string; - max-elements 1024; - min-elements 1; - } - leaf type { - type string; - } - // TODO(robjs): unique for deviation - leaf units { - type uint16; - units "nanofish per millenium"; - } - } - - container target { - container delete { - uses substmts; - } - } - - deviation /target/delete/config { - deviate delete { - config true; - } - } - - deviation /target/delete/default { - deviate delete { - default "fish"; - } - } - - deviation /target/delete/mandatory { - deviate delete { - mandatory false; - } - } - - deviation /target/delete/min-elements { - deviate delete { - min-elements 1000; - } - } - - deviation /target/delete/max-elements { - deviate delete { - max-elements 1000; - } - } - - deviation /target/delete/max-and-min-elements { - deviate delete { - max-elements 1024; - min-elements 1; - } - } - - deviation /target/delete/units { - deviate delete { - units "nanofish per millenium"; - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/testdata/deviate-notsupported.yang b/src/webui/internal/goyang/pkg/yang/testdata/deviate-notsupported.yang deleted file mode 100644 index 5c59af2db..000000000 --- a/src/webui/internal/goyang/pkg/yang/testdata/deviate-notsupported.yang +++ /dev/null @@ -1,42 +0,0 @@ -module deviate { - prefix "d"; - namespace "urn:d"; - - grouping substmts { - container child { - leaf zzz { type string; } - } - } - - container target { - uses substmts; - } - - list target-list { - key "k"; - - leaf k { type string; } - uses substmts; - } - - leaf a-leaf { type string; } - leaf a-leaflist { type string; } - - leaf survivor { type string; } - - deviation /target { - deviate not-supported; - } - - deviation /target-list { - deviate not-supported; - } - - deviation /a-leaf { - deviate not-supported; - } - - deviation /a-leaflist { - deviate not-supported; - } -} diff --git a/src/webui/internal/goyang/pkg/yang/testdata/deviate-replace.yang b/src/webui/internal/goyang/pkg/yang/testdata/deviate-replace.yang deleted file mode 100644 index 9f1166e05..000000000 --- a/src/webui/internal/goyang/pkg/yang/testdata/deviate-replace.yang +++ /dev/null @@ -1,106 +0,0 @@ -module deviate { - prefix "d"; - namespace "urn:d"; - - grouping substmts { - leaf config { - type string; - config true; - } - leaf default { - type string; - default "fish"; - } - leaf-list default-list { - type string; - default "fish"; - default "sticks"; - } - leaf mandatory { - type string; - mandatory false; - } - leaf-list max-elements { - type string; - max-elements 1000; - } - leaf-list min-elements { - type string; - min-elements 1000; - } - leaf-list max-and-min-elements { - type string; - max-elements 1024; - min-elements 1; - } - leaf type { - type string; - } - // TODO(robjs): unique for deviation - leaf units { - type uint16; - units "nanofish per millenium"; - } - } - - container target { - container replace { - uses substmts; - } - } - - deviation /target/replace/config { - deviate replace { - config false; - } - } - - deviation /target/replace/default { - deviate replace { - default "a default value"; - } - } - - deviation /target/replace/default-list { - deviate replace { - default "nematodes"; - } - } - - deviation /target/replace/mandatory { - deviate replace { - mandatory true; - } - } - - deviation /target/replace/min-elements { - deviate replace { - min-elements 42; - } - } - - deviation /target/replace/max-elements { - deviate replace { - max-elements 42; - } - } - - deviation /target/replace/max-and-min-elements { - deviate replace { - max-elements 42; - min-elements 42; - } - } - - deviation /target/replace/type { - deviate replace { - type uint16; - } - } - - deviation /target/replace/units { - deviate replace { - units "fish per second"; - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/testdata/deviate.yang b/src/webui/internal/goyang/pkg/yang/testdata/deviate.yang deleted file mode 100644 index d8fbaa60e..000000000 --- a/src/webui/internal/goyang/pkg/yang/testdata/deviate.yang +++ /dev/null @@ -1,81 +0,0 @@ -module deviate { - prefix "d"; - namespace "urn:d"; - - typedef derived-string { - type string; - default "barnacles"; - } - - grouping substmts { - leaf config { type string; } - leaf default { type string; } - leaf default-typedef { type derived-string; } - leaf-list default-list { type string; default "foo"; default "bar"; } - leaf-list default-list-typedef-default { type derived-string; } - leaf mandatory { type string; } - leaf-list max-elements { type string; } - leaf-list min-elements { type string; } - leaf-list max-and-min-elements { type string; } - leaf type { type string; } - // TODO(robjs): unique requires a list target - leaf units { type uint16; } - } - - container target { - container add { - uses substmts; - } - } - - deviation /target/add/config { - deviate add { - config false; - } - } - - deviation /target/add/default { - deviate add { - default "a default value"; - } - } - - deviation /target/add/default-list { - deviate add { - default "foo"; - // TODO(wenovus): support multiple default statements for deviate. - //default "baz"; - } - } - - deviation /target/add/mandatory { - deviate add { - mandatory true; - } - } - - deviation /target/add/min-elements { - deviate add { - min-elements 42; - } - } - - deviation /target/add/max-elements { - deviate add { - max-elements 42; - } - } - - deviation /target/add/max-and-min-elements { - deviate add { - max-elements 42; - min-elements 42; - } - } - - deviation /target/add/units { - deviate add { - units "fish per second"; - } - } -} diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/blue.yang b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/blue.yang deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/blue@2000-10-10.yang b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/blue@2000-10-10.yang deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/dirdir/red@2022-02-22.yang b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/dirdir/red@2022-02-22.yang deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/red@2020-02-02.yang b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/red@2020-02-02.yang deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/red@2020-02-20.yang b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/dir/red@2020-02-20.yang deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/non-standard.name b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/non-standard.name deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/red@2010-10-10.yang b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/red@2010-10-10.yang deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/red@2222-2-22.yang b/src/webui/internal/goyang/pkg/yang/testdata/find-file-test/red@2222-2-22.yang deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/webui/internal/goyang/pkg/yang/types.go b/src/webui/internal/goyang/pkg/yang/types.go deleted file mode 100644 index 2475fa4a4..000000000 --- a/src/webui/internal/goyang/pkg/yang/types.go +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// This file implements the functions relating to types and typedefs. - -import ( - "errors" - "fmt" - "regexp/syntax" - "sync" -) - -// A typeDictionary is a dictionary of all Typedefs defined in all Typedefers. -// A map of Nodes is used rather than a map of Typedefers to simplify usage -// when traversing up a Node tree. -type typeDictionary struct { - mu sync.Mutex - dict map[Node]map[string]*Typedef - // identities contains a dictionary of resolved identities. - identities identityDictionary -} - -func newTypeDictionary() *typeDictionary { - return &typeDictionary{ - dict: map[Node]map[string]*Typedef{}, - identities: identityDictionary{dict: map[string]resolvedIdentity{}}, - } -} - -// add adds an entry to the typeDictionary d. -func (d *typeDictionary) add(n Node, name string, td *Typedef) { - defer d.mu.Unlock() - d.mu.Lock() - if d.dict[n] == nil { - d.dict[n] = map[string]*Typedef{} - } - d.dict[n][name] = td -} - -// find returns the Typedef name define in node n, or nil. -func (d *typeDictionary) find(n Node, name string) *Typedef { - defer d.mu.Unlock() - d.mu.Lock() - if d.dict[n] == nil { - return nil - } - return d.dict[n][name] -} - -// findExternal finds the externally-defined typedef name in a module imported -// by n's root with the specified prefix. -func (d *typeDictionary) findExternal(n Node, prefix, name string) (*Typedef, error) { - root := FindModuleByPrefix(n, prefix) - if root == nil { - return nil, fmt.Errorf("%s: unknown prefix: %s for type %s", Source(n), prefix, name) - } - if td := d.find(root, name); td != nil { - return td, nil - } - if prefix != "" { - name = prefix + ":" + name - } - return nil, fmt.Errorf("%s: unknown type %s", Source(n), name) -} - -// typedefs returns a slice of all typedefs in d. -func (d *typeDictionary) typedefs() []*Typedef { - var tds []*Typedef - defer d.mu.Unlock() - d.mu.Lock() - for _, dict := range d.dict { - for _, td := range dict { - tds = append(tds, td) - } - } - return tds -} - -// addTypedefs is called from BuildAST after each Typedefer is defined. There -// are no error conditions in this process as it is simply used to build up the -// typedef dictionary. -func (d *typeDictionary) addTypedefs(t Typedefer) { - for _, td := range t.Typedefs() { - d.add(t, td.Name, td) - } -} - -// resolveTypedefs is called after all of modules and submodules have been read, -// as well as their imports and includes. It resolves all typedefs found in all -// modules and submodules read in. -func (d *typeDictionary) resolveTypedefs() []error { - var errs []error - - // When resolve typedefs, we may need to look up other typedefs. - // We gather all typedefs into a slice so we don't deadlock on - // typeDict. - for _, td := range d.typedefs() { - errs = append(errs, td.resolve(d)...) - } - return errs -} - -// resolve creates a YangType for t, if not already done. Resolving t -// requires resolving the Type that t is based on. -func (t *Typedef) resolve(d *typeDictionary) []error { - // If we have no parent we are a base type and - // are already resolved. - if t.Parent == nil || t.YangType != nil { - return nil - } - - if errs := t.Type.resolve(d); len(errs) != 0 { - return errs - } - - // Make a copy of the YangType we are based on and then - // update it with local information. - y := *t.Type.YangType - y.Name = t.Name - y.Base = t.Type - - if t.Units != nil { - y.Units = t.Units.Name - } - if t.Default != nil { - y.HasDefault = true - y.Default = t.Default.Name - } - - if t.Type.IdentityBase != nil { - // We need to copy over the IdentityBase statement if the type has one - if idBase, err := RootNode(t).findIdentityBase(t.Type.IdentityBase.Name); err == nil { - y.IdentityBase = idBase.Identity - } else { - return []error{fmt.Errorf("could not resolve identity base for typedef: %s", t.Type.IdentityBase.Name)} - } - } - - // If we changed something, we are the new root. - if y.Root == t.Type.YangType || !y.Equal(y.Root) { - y.Root = &y - } - t.YangType = &y - return nil -} - -// resolve resolves Type t, as well as the underlying typedef for t. If t -// cannot be resolved then one or more errors are returned. -func (t *Type) resolve(d *typeDictionary) (errs []error) { - if t.YangType != nil { - return nil - } - - // If t.Name is a base type then td will not be nil, otherwise - // td will be nil and of type *Typedef. - td := BaseTypedefs[t.Name] - - prefix, name := getPrefix(t.Name) - root := RootNode(t) - rootPrefix := root.GetPrefix() - - source := "unknown" -check: - switch { - case td != nil: - source = "builtin" - // This was a base type - case prefix == "" || rootPrefix == prefix: - source = "local" - // If we have no prefix, or the prefix is what we call our own - // root, then we look in our ancestors for a typedef of name. - for n := Node(t); n != nil; n = n.ParentNode() { - if td = d.find(n, name); td != nil { - break check - } - } - // We need to check our sub-modules as well - for _, in := range root.Include { - if td = d.find(in.Module, name); td != nil { - break check - } - } - var pname string - switch { - case prefix == "", prefix == root.Prefix.Name: - pname = root.Prefix.Name + ":" + t.Name - default: - pname = fmt.Sprintf("%s[%s]:%s", prefix, root.Prefix.Name, t.Name) - } - - return []error{fmt.Errorf("%s: unknown type: %s", Source(t), pname)} - - default: - source = "imported" - // prefix is not local to our module, so we have to go find - // what module it is part of and if it is defined at the top - // level of that module. - var err error - td, err = d.findExternal(t, prefix, name) - if err != nil { - return []error{err} - } - } - if errs := td.resolve(d); len(errs) > 0 { - return errs - } - - // Make a copy of the typedef we are based on so we can - // augment it. - if td.YangType == nil { - return []error{fmt.Errorf("%s: no YangType defined for %s %s", Source(td), source, td.Name)} - } - y := *td.YangType - - y.Base = td.Type - t.YangType = &y - - if v := t.RequireInstance; v != nil { - b, err := v.asBool() - if err != nil { - errs = append(errs, err) - } - y.OptionalInstance = !b - } - if v := t.Path; v != nil { - y.Path = v.asString() - } - isDecimal64 := y.Kind == Ydecimal64 && (t.Name == "decimal64" || y.FractionDigits != 0) - switch { - case isDecimal64 && y.FractionDigits != 0: - if t.FractionDigits != nil { - return append(errs, fmt.Errorf("%s: overriding of fraction-digits not allowed", Source(t))) - } - // FractionDigits already set via type inheritance. - case isDecimal64: - // If we are directly of type decimal64 then we must specify - // fraction-digits in the range from 1-18. - i, err := t.FractionDigits.asRangeInt(1, 18) - if err != nil { - errs = append(errs, fmt.Errorf("%s: %v", Source(t), err)) - } - y.FractionDigits = int(i) - // We only know to how to populate Range after knowing the - // fractional digit value. - y.Range = YangRange{{ - Number{Value: AbsMinInt64, Negative: true, FractionDigits: uint8(i)}, - Number{Value: MaxInt64, FractionDigits: uint8(i)}, - }} - case t.FractionDigits != nil: - errs = append(errs, fmt.Errorf("%s: fraction-digits only allowed for decimal64 values", Source(t))) - case y.Kind == Yidentityref: - if source != "builtin" { - // This is a typedef that refers to an identityref, so we want to simply - // maintain the base that the typedef resolution provided - break - } - - if t.IdentityBase == nil { - errs = append(errs, fmt.Errorf("%s: an identityref must specify a base", Source(t))) - break - } - - root := RootNode(t.Parent) - resolvedBase, baseErr := root.findIdentityBase(t.IdentityBase.Name) - if baseErr != nil { - errs = append(errs, baseErr...) - break - } - - if resolvedBase.Identity == nil { - errs = append(errs, fmt.Errorf("%s: identity has a null base", t.IdentityBase.Name)) - break - } - y.IdentityBase = resolvedBase.Identity - } - - if t.Range != nil { - yr, err := y.Range.parseChildRanges(t.Range.Name, isDecimal64, uint8(y.FractionDigits)) - switch { - case err != nil: - errs = append(errs, fmt.Errorf("%s: bad range: %v", Source(t.Range), err)) - case yr.Equal(y.Range): - default: - y.Range = yr - } - } - - if t.Length != nil { - parentRange := Uint64Range - if y.Length != nil { - parentRange = y.Length - } - yr, err := parentRange.parseChildRanges(t.Length.Name, false, 0) - switch { - case err != nil: - errs = append(errs, fmt.Errorf("%s: bad length: %v", Source(t.Length), err)) - case yr.Equal(y.Length): - default: - for _, r := range yr { - if r.Min.Negative { - errs = append(errs, fmt.Errorf("%s: negative length: %v", Source(t.Length), yr)) - break - } - } - y.Length = yr - } - } - - set := func(e *EnumType, name string, value *Value) error { - if value == nil { - return e.SetNext(name) - } - n, err := ParseInt(value.Name) - if err != nil { - return err - } - i, err := n.Int() - if err != nil { - return err - } - return e.Set(name, i) - } - - if len(t.Enum) > 0 { - enum := NewEnumType() - for _, e := range t.Enum { - if err := set(enum, e.Name, e.Value); err != nil { - errs = append(errs, fmt.Errorf("%s: %v", Source(e), err)) - } - } - y.Enum = enum - } - - if len(t.Bit) > 0 { - bit := NewBitfield() - for _, e := range t.Bit { - if err := set(bit, e.Name, e.Position); err != nil { - errs = append(errs, fmt.Errorf("%s: %v", Source(e), err)) - } - } - y.Bit = bit - } - - // Append any newly found patterns to the end of the list of patterns. - // Patterns are ANDed according to section 9.4.6. If all the patterns - // declared by t were also declared by the type t is based on, then - // no patterns are added. - seenPatterns := map[string]bool{} - for _, p := range y.Pattern { - seenPatterns[p] = true - } - seenPOSIXPatterns := map[string]bool{} - for _, p := range y.POSIXPattern { - seenPOSIXPatterns[p] = true - } - - // First parse out the pattern statements. - // These patterns are not checked because there is no support for W3C regexes by Go. - for _, pv := range t.Pattern { - if !seenPatterns[pv.Name] { - seenPatterns[pv.Name] = true - y.Pattern = append(y.Pattern, pv.Name) - } - } - - // Then, parse out the posix-pattern statements, if they exist. - // A YANG module could make use of either or both, so we deal with each separately. - posixPatterns, err := MatchingExtensions(t, "openconfig-extensions", "posix-pattern") - if err != nil { - return []error{err} - } - - checkPattern := func(n Node, p string, flags syntax.Flags) { - if _, err := syntax.Parse(p, flags); err != nil { - if re, ok := err.(*syntax.Error); ok { - // Error adds "error parsing regexp" to - // the error, re.Code is the real error. - err = errors.New(re.Code.String()) - } - errs = append(errs, fmt.Errorf("%s: bad pattern: %v: %s", Source(n), err, p)) - } - } - for _, ext := range posixPatterns { - checkPattern(ext, ext.Argument, syntax.POSIX) - if !seenPOSIXPatterns[ext.Argument] { - seenPOSIXPatterns[ext.Argument] = true - y.POSIXPattern = append(y.POSIXPattern, ext.Argument) - } - } - - // I don't know of an easy way to use a type as a key to a map, - // so we have to check equality the hard way. -looking: - for _, ut := range t.Type { - errs = append(errs, ut.resolve(d)...) - if ut.YangType != nil { - for _, yt := range y.Type { - if ut.YangType.Equal(yt) { - continue looking - } - } - y.Type = append(y.Type, ut.YangType) - } - } - - // If we changed something, we are the new root. - if !y.Equal(y.Root) { - y.Root = &y - } - - return errs -} diff --git a/src/webui/internal/goyang/pkg/yang/types_builtin.go b/src/webui/internal/goyang/pkg/yang/types_builtin.go deleted file mode 100644 index f87e1b93a..000000000 --- a/src/webui/internal/goyang/pkg/yang/types_builtin.go +++ /dev/null @@ -1,700 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -// This module contains all the builtin types as well as types related -// to types (such as ranges, enums, etc). - -import ( - "errors" - "fmt" - "math" - "sort" - "strconv" - "strings" -) - -// This file handles interpretation of types - -// These are the default ranges defined by the YANG standard. -var ( - Int8Range = mustParseRangesInt("-128..127") - Int16Range = mustParseRangesInt("-32768..32767") - Int32Range = mustParseRangesInt("-2147483648..2147483647") - Int64Range = mustParseRangesInt("-9223372036854775808..9223372036854775807") - - Uint8Range = mustParseRangesInt("0..255") - Uint16Range = mustParseRangesInt("0..65535") - Uint32Range = mustParseRangesInt("0..4294967295") - Uint64Range = mustParseRangesInt("0..18446744073709551615") -) - -const ( - // MaxInt64 corresponds to the maximum value of a signed int64. - MaxInt64 = 1<<63 - 1 - // MinInt64 corresponds to the maximum value of a signed int64. - MinInt64 = -1 << 63 - // Min/MaxDecimal64 are the max/min decimal64 values. - MinDecimal64 float64 = -922337203685477580.8 - MaxDecimal64 float64 = 922337203685477580.7 - // AbsMinInt64 is the absolute value of MinInt64. - AbsMinInt64 = 1 << 63 - // MaxEnum is the maximum value of an enumeration. - MaxEnum = 1<<31 - 1 - // MinEnum is the minimum value of an enumeration. - MinEnum = -1 << 31 - // MaxBitfieldSize is the maximum number of bits in a bitfield. - MaxBitfieldSize = 1 << 32 - // MaxFractionDigits is the maximum number of fractional digits as per RFC6020 Section 9.3.4. - MaxFractionDigits uint8 = 18 - - space18 = "000000000000000000" // used for prepending 0's -) - -// A Number is either an integer the range of [-(1<<64) - 1, (1<<64)-1], or a -// YANG decimal conforming to https://tools.ietf.org/html/rfc6020#section-9.3.4. -type Number struct { - // Absolute value of the number. - Value uint64 - // Number of fractional digits. - // 0 means it's an integer. For decimal64 it falls within [1, 18]. - FractionDigits uint8 - // Negative indicates whether the number is negative. - Negative bool -} - -// IsDecimal reports whether n is a decimal number. -func (n Number) IsDecimal() bool { - return n.FractionDigits != 0 -} - -// String returns n as a string in decimal. -func (n Number) String() string { - out := strconv.FormatUint(n.Value, 10) - - if n.IsDecimal() { - if fd := int(n.FractionDigits); fd > 0 { - ofd := len(out) - fd - if ofd <= 0 { - // We want 0.1 not .1 - out = space18[:-ofd+1] + out - ofd = 1 - } - out = out[:ofd] + "." + out[ofd:] - } - } - if n.Negative { - out = "-" + out - } - - return out -} - -// Int returns n as an int64. It returns an error if n overflows an int64 or -// the number is decimal. -func (n Number) Int() (int64, error) { - if n.IsDecimal() { - return 0, errors.New("called Int() on decimal64 value") - } - if n.Negative { - return -int64(n.Value), nil - } - if n.Value <= MaxInt64 { - return int64(n.Value), nil - } - return 0, errors.New("signed integer overflow") -} - -// addQuantum adds the smallest quantum to n without checking overflow. -func (n Number) addQuantum(i uint64) Number { - switch n.Negative { - case true: - if n.Value <= i { - n.Value = i - n.Value - n.Negative = false - } else { - n.Value -= i - } - case false: - n.Value += i - } - return n -} - -// Less returns true if n is less than m. Panics if n and m are a mix of integer -// and decimal. -func (n Number) Less(m Number) bool { - switch { - case n.Negative && !m.Negative: - return true - case !n.Negative && m.Negative: - return false - } - - nt, mt := n.Trunc(), m.Trunc() - lt := nt < mt - if nt == mt { - nf, mf := n.frac(), m.frac() - if nf == mf { - return false - } - lt = nf < mf - } - - if n.Negative { - return !lt - } - return lt -} - -// Equal returns true if n is equal to m. -func (n Number) Equal(m Number) bool { - return !n.Less(m) && !m.Less(n) -} - -// Trunc returns the whole part of abs(n) as a signed integer. -func (n Number) Trunc() uint64 { - nv := n.Value - e := pow10(n.FractionDigits) - return nv / e -} - -// frac returns the fraction part with a precision of 18 fractional digits. -// E.g. if n is 3.1 then n.frac() returns 100,000,000,000,000,000 -func (n Number) frac() uint64 { - frac := n.FractionDigits - i := n.Trunc() * pow10(frac) - return (n.Value - i) * pow10(uint8(18-frac)) -} - -// YRange is a single range of consecutive numbers, inclusive. -type YRange struct { - Min Number - Max Number -} - -// Valid returns false if r is not a valid range (min > max). -func (r YRange) Valid() bool { - return !r.Max.Less(r.Min) -} - -// String returns r as a string using YANG notation, either a simple -// value if min == max or min..max. -func (r YRange) String() string { - if r.Min.Equal(r.Max) { - return r.Min.String() - } - return r.Min.String() + ".." + r.Max.String() -} - -// Equal compares whether two YRanges are equal. -func (r YRange) Equal(s YRange) bool { - return r.Min.Equal(s.Min) && r.Max.Equal(s.Max) -} - -// A YangRange is a set of non-overlapping ranges. -type YangRange []YRange - -// String returns the ranges r using YANG notation. Individual ranges -// are separated by pipes (|). -func (r YangRange) String() string { - s := make([]string, len(r)) - for i, r := range r { - s[i] = r.String() - } - return strings.Join(s, "|") -} - -func (r YangRange) Len() int { return len(r) } -func (r YangRange) Swap(i, j int) { r[i], r[j] = r[j], r[i] } -func (r YangRange) Less(i, j int) bool { - switch { - case r[i].Min.Less(r[j].Min): - return true - case r[j].Min.Less(r[i].Min): - return false - default: - return r[i].Max.Less(r[j].Max) - } -} - -// Validate returns an error if r has either an invalid range or has -// overlapping ranges. -// r is expected to be sorted use YangRange.Sort() -func (r YangRange) Validate() error { - if !sort.IsSorted(r) { - return errors.New("range not sorted") - } - switch { - case len(r) == 0: - return nil - case !r[0].Valid(): - return errors.New("invalid number") - } - p := r[0] - - for _, n := range r[1:] { - if n.Min.Less(p.Max) { - return errors.New("overlapping ranges") - } - } - return nil -} - -// Sort r. Must be called before Validate and coalesce if unsorted -func (r YangRange) Sort() { - sort.Sort(r) -} - -// Equal returns true if ranges r and q are identically equivalent. -// TODO(borman): should we coalesce ranges in the comparison? -func (r YangRange) Equal(q YangRange) bool { - if len(r) != len(q) { - return false - } - for i, r := range r { - if !r.Equal(q[i]) { - return false - } - } - return true -} - -// Contains returns true if all possible values in s are also possible values -// in r. An empty range is assumed to be min..max when it is the receiver -// argument. -func (r YangRange) Contains(s YangRange) bool { - if len(r) == 0 || len(s) == 0 { - return true - } - - // Check if every range in s is subsumed under r. - // Both range lists should be in order and non-adjacent (coalesced). - ri := 0 - for _, ss := range s { - for r[ri].Max.Less(ss.Min) { - ri++ - if ri == len(r) { - return false - } - } - if ss.Min.Less(r[ri].Min) || r[ri].Max.Less(ss.Max) { - return false - } - } - return true -} - -// FromInt creates a Number from an int64. -func FromInt(i int64) Number { - if i < 0 { - return Number{Negative: true, Value: uint64(-i)} - } - return Number{Value: uint64(i)} -} - -// FromUint creates a Number from a uint64. -func FromUint(i uint64) Number { - return Number{Value: i} -} - -// FromFloat creates a Number from a float64. Input values with absolute value -// outside the boundaries specified for the decimal64 value specified in -// RFC6020/RFC7950 are clamped down to the closest boundary value. -func FromFloat(f float64) Number { - if f > MaxDecimal64 { - return Number{ - Value: FromInt(MaxInt64).Value, - FractionDigits: 1, - } - } - if f < MinDecimal64 { - return Number{ - Negative: true, - Value: FromInt(MaxInt64).Value, - FractionDigits: 1, - } - } - - // Per RFC7950/6020, fraction-digits must be at least 1. - fracDig := uint8(1) - f *= 10.0 - for ; Frac(f) != 0.0 && fracDig <= MaxFractionDigits; fracDig++ { - f *= 10.0 - } - negative := false - if f < 0 { - negative = true - f = -f - } - v := uint64(f) - - return Number{Negative: negative, Value: v, FractionDigits: fracDig} -} - -// ParseInt returns s as a Number with FractionDigits=0. -// octal, or hexadecimal using the standard prefix notations (e.g., 0 and 0x) -func ParseInt(s string) (Number, error) { - s = strings.TrimSpace(s) - var n Number - switch s { - case "": - return n, errors.New("converting empty string to number") - case "+", "-": - return n, errors.New("sign with no value") - } - - ns := s - switch s[0] { - case '+': - ns = s[1:] - case '-': - n.Negative = true - ns = s[1:] - } - - var err error - n.Value, err = strconv.ParseUint(ns, 0, 64) - return n, err -} - -// ParseDecimal returns s as a Number with a non-zero FractionDigits. -// octal, or hexadecimal using the standard prefix notations (e.g., 0 and 0x) -func ParseDecimal(s string, fracDigRequired uint8) (n Number, err error) { - s = strings.TrimSpace(s) - switch s { - case "": - return n, errors.New("converting empty string to number") - case "+", "-": - return n, errors.New("sign with no value") - } - - return decimalValueFromString(s, fracDigRequired) -} - -// decimalValueFromString returns a decimal Number representation of numStr. -// fracDigRequired is used to set the number of fractional digits, which must -// be at least the greatest precision seen in numStr. -// which must be between 1 and 18. -// numStr must conform to Section 9.3.4. -func decimalValueFromString(numStr string, fracDigRequired uint8) (n Number, err error) { - if fracDigRequired > MaxFractionDigits || fracDigRequired < 1 { - return n, fmt.Errorf("invalid number of fraction digits %d > max of %d, minimum 1", fracDigRequired, MaxFractionDigits) - } - - s := numStr - dx := strings.Index(s, ".") - var fracDig uint8 - if dx >= 0 { - fracDig = uint8(len(s) - 1 - dx) - // remove first decimal, if dx > 1, will fail ParseInt below - s = s[:dx] + s[dx+1:] - } - - if fracDig > fracDigRequired { - return n, fmt.Errorf("%s has too much precision, expect <= %d fractional digits", s, fracDigRequired) - } - - s += space18[:fracDigRequired-fracDig] - - v, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return n, fmt.Errorf("%s is not a valid decimal number: %s", numStr, err) - } - - negative := false - if v < 0 { - negative = true - v = -v - } - - return Number{Value: uint64(v), FractionDigits: fracDigRequired, Negative: negative}, nil -} - -// ParseRangesInt parses s into a series of ranges. Each individual range is in s -// is separated by the pipe character (|). The min and max value of a range -// are separated by "..". An error is returned if the range is invalid. The -// output range is sorted and coalesced. -func ParseRangesInt(s string) (YangRange, error) { - return YangRange{}.parseChildRanges(s, false, 0) -} - -// ParseRangesDecimal parses s into a series of ranges. Each individual range is in s -// is separated by the pipe character (|). The min and max value of a range -// are separated by "..". An error is returned if the range is invalid. The -// output range is sorted and coalesced. -func ParseRangesDecimal(s string, fracDigRequired uint8) (YangRange, error) { - return YangRange{}.parseChildRanges(s, true, fracDigRequired) -} - -// parseChildRanges parses a child ranges statement 's' into a series of ranges -// based on an already-parsed parent YangRange. Each individual range is in s -// is separated by the pipe character (|). The min and max value of a range are -// separated by "..". An error is returned if the child ranges are not -// equally-limiting or more limiting than the parent range -// (rfc7950#section-9.2.5). The output range is sorted and coalesced. -// fracDigRequired is ignored when decimal=false. -func (y YangRange) parseChildRanges(s string, decimal bool, fracDigRequired uint8) (YangRange, error) { - parseNumber := func(s string) (Number, error) { - switch { - case s == "max": - if len(y) == 0 { - return Number{}, errors.New("cannot resolve 'max' keyword using an empty YangRange parent object") - } - max := y[len(y)-1].Max - max.FractionDigits = fracDigRequired - return max, nil - case s == "min": - if len(y) == 0 { - return Number{}, errors.New("cannot resolve 'min' keyword using an empty YangRange parent object") - } - min := y[0].Min - min.FractionDigits = fracDigRequired - return min, nil - case decimal: - return ParseDecimal(s, fracDigRequired) - default: - return ParseInt(s) - } - } - - parts := strings.Split(s, "|") - r := make(YangRange, len(parts)) - for i, s := range parts { - parts := strings.Split(s, "..") - min, err := parseNumber(strings.TrimSpace(parts[0])) - if err != nil { - return nil, err - } - var max Number - switch len(parts) { - case 1: - max = min - case 2: - if max, err = parseNumber(strings.TrimSpace(parts[1])); err != nil { - return nil, err - } - default: - return nil, fmt.Errorf("too many '..' in %s", s) - } - if max.Less(min) { - return nil, fmt.Errorf("range boundaries out of order (%s less than %s): %s", max, min, s) - } - r[i] = YRange{min, max} - } - r.Sort() - r = coalesce(r) - - if !y.Contains(r) { - return nil, fmt.Errorf("%v not within %v", s, y) - } - - if err := r.Validate(); err != nil { - return nil, err - } - return r, nil -} - -// coalesce coalesces r into as few ranges as possible. For example, -// 1..5|6..10 would become 1..10. r is assumed to be sorted. -func coalesce(r YangRange) YangRange { - // coalesce the ranges if we have more than 1. - if len(r) < 2 { - return r - } - cr := make(YangRange, len(r)) - i := 0 - cr[i] = r[0] - for _, r1 := range r[1:] { - // r1.Min is always at least as large as cr[i].Min - // Cases are: - // r1 is contained in cr[i] - // r1 starts inside of cr[i] - // r1.Min cr[i].Max+1 - // r1 is beyond cr[i] - if cr[i].Max.addQuantum(1).Less(r1.Min) { - // r1 starts after cr[i], this is a new range - i++ - cr[i] = r1 - } else if cr[i].Max.Less(r1.Max) { - cr[i].Max = r1.Max - } - } - return cr[:i+1] -} - -func mustParseRangesInt(s string) YangRange { - r, err := ParseRangesInt(s) - if err != nil { - panic(err) - } - return r -} - -func mustParseRangesDecimal(s string, fracDigRequired uint8) YangRange { - r, err := ParseRangesDecimal(s, fracDigRequired) - if err != nil { - panic(err) - } - return r -} - -// Frac returns the fractional part of f. -func Frac(f float64) float64 { - return f - math.Trunc(f) -} - -// pow10 returns 10^e without checking for overflow. -func pow10(e uint8) uint64 { - var out uint64 = 1 - for i := uint8(0); i < e; i++ { - out *= 10 - } - return out -} - -// A EnumType represents a mapping of strings to integers. It is used both -// for enumerations as well as bitfields. -type EnumType struct { - last int64 // maximum value assigned thus far - min int64 // minimum value allowed - max int64 // maximum value allowed - unique bool // numeric values must be unique (enums) - ToString map[int64]string `json:",omitempty"` // map of enum entries by value (integer) - ToInt map[string]int64 `json:",omitempty"` // map of enum entries by name (string) -} - -// NewEnumType returns an initialized EnumType. -func NewEnumType() *EnumType { - return &EnumType{ - last: -1, // +1 will start at 0 - min: MinEnum, - max: MaxEnum, - unique: true, - ToString: map[int64]string{}, - ToInt: map[string]int64{}, - } -} - -// NewBitfield returns an EnumType initialized as a bitfield. Multiple string -// values may map to the same numeric values. Numeric values must be small -// non-negative integers. -func NewBitfield() *EnumType { - return &EnumType{ - last: -1, // +1 will start at 0 - min: 0, - max: MaxBitfieldSize - 1, - ToString: map[int64]string{}, - ToInt: map[string]int64{}, - } -} - -// Set sets name in e to the provided value. Set returns an error if the value -// is invalid, name is already signed, or when used as an enum rather than a -// bitfield, the value has previousl been used. When two different names are -// assigned to the same value, the conversion from value to name will result in -// the most recently assigned name. -func (e *EnumType) Set(name string, value int64) error { - if _, ok := e.ToInt[name]; ok { - return fmt.Errorf("field %s already assigned", name) - } - if oname, ok := e.ToString[value]; e.unique && ok { - return fmt.Errorf("fields %s and %s conflict on value %d", name, oname, value) - } - if value < e.min { - return fmt.Errorf("value %d for %s too small (minimum is %d)", value, name, e.min) - } - if value > e.max { - return fmt.Errorf("value %d for %s too large (maximum is %d)", value, name, e.max) - } - e.ToString[value] = name - e.ToInt[name] = value - if value >= e.last { - e.last = value - } - return nil -} - -// SetNext sets the name in e using the next possible value that is greater than -// all previous values. -func (e *EnumType) SetNext(name string) error { - if e.last == MaxEnum { - return fmt.Errorf("enum %q must specify a value since previous enum is the maximum value allowed", name) - } - return e.Set(name, e.last+1) -} - -// Name returns the name in e associated with value. The empty string is -// returned if no name has been assigned to value. -func (e *EnumType) Name(value int64) string { return e.ToString[value] } - -// Value returns the value associated with name in e associated. 0 is returned -// if name is not in e, or if it is the first value in an unnumbered enum. Use -// IsDefined to definitively confirm name is in e. -func (e *EnumType) Value(name string) int64 { return e.ToInt[name] } - -// IsDefined returns true if name is defined in e, else false. -func (e *EnumType) IsDefined(name string) bool { - _, defined := e.ToInt[name] - return defined -} - -// Names returns the sorted list of enum string names. -func (e *EnumType) Names() []string { - names := make([]string, len(e.ToInt)) - i := 0 - for name := range e.ToInt { - names[i] = name - i++ - } - sort.Strings(names) - return names -} - -type int64Slice []int64 - -func (p int64Slice) Len() int { return len(p) } -func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] } -func (p int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// Values returns the sorted list of enum values. -func (e *EnumType) Values() []int64 { - values := make([]int64, len(e.ToInt)) - i := 0 - for _, value := range e.ToInt { - values[i] = value - i++ - } - sort.Sort(int64Slice(values)) - return values -} - -// NameMap returns a map of names to values. -func (e *EnumType) NameMap() map[string]int64 { - m := make(map[string]int64, len(e.ToInt)) - for name, value := range e.ToInt { - m[name] = value - } - return m -} - -// ValueMap returns a map of values to names. -func (e *EnumType) ValueMap() map[int64]string { - m := make(map[int64]string, len(e.ToString)) - for name, value := range e.ToString { - m[name] = value - } - return m -} diff --git a/src/webui/internal/goyang/pkg/yang/types_builtin_test.go b/src/webui/internal/goyang/pkg/yang/types_builtin_test.go deleted file mode 100644 index 5ae654da3..000000000 --- a/src/webui/internal/goyang/pkg/yang/types_builtin_test.go +++ /dev/null @@ -1,915 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "encoding/json" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/openconfig/gnmi/errdiff" -) - -const ( - maxUint64 uint64 = 18446744073709551615 - maxUint32 = 0xFFFFFFFF - maxUint16 = 0xFFFF - maxUint8 = 0xFF - maxInt32 = 1<<31 - 1 - minInt32 = -1 << 31 - maxInt16 = 1<<15 - 1 - minInt16 = -1 << 15 - maxInt8 = 1<<7 - 1 - minInt8 = -1 << 7 -) - -// R is a test helper for creating an int-based YRange. -func R(a, b int64) YRange { - return YRange{FromInt(a), FromInt(b)} -} - -// Rf is a test helper for creating a float-based YRange. -func Rf(a, b int64, fracDig uint8) YRange { - n1 := Number{Value: uint64(a), FractionDigits: fracDig} - n2 := Number{Value: uint64(b), FractionDigits: fracDig} - if a < 0 { - n1.Value = uint64(-a) - n1.Negative = true - } - if b < 0 { - n2.Value = uint64(-b) - n2.Negative = true - } - return YRange{n1, n2} -} - -func TestFromFloat(t *testing.T) { - tests := []struct { - desc string - in float64 - want Number - }{{ - desc: "positive - no decimals", - in: 10.0, - want: Number{ - Negative: false, - Value: 10, - FractionDigits: 0, - }, - }, { - desc: "positive - decimals", - in: 10.15, - want: Number{ - Negative: false, - Value: 1015, - FractionDigits: 2, - }, - }, { - desc: "negative - no decimals", - in: -10.0, - want: Number{ - Negative: true, - Value: 10, - FractionDigits: 0, - }, - }, { - desc: "negative - decimals", - in: -10.15, - want: Number{ - Negative: true, - Value: 1015, - FractionDigits: 2, - }, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - if got := FromFloat(tt.in); !cmp.Equal(got, tt.want) { - t.Fatalf("FromFloat(%v): did not get expected value, got: %+v, want: %+v", tt.in, got, tt.want) - } - }) - } -} - -func TestNumberInt(t *testing.T) { - tests := []struct { - desc string - in Number - want int64 - wantErr bool - }{{ - desc: "zero", - in: FromInt(0), - want: 0, - }, { - desc: "positive", - in: FromInt(42), - want: 42, - }, { - desc: "negative", - in: FromInt(-42), - want: -42, - }, { - desc: "decimal", - in: FromFloat(42), - wantErr: true, - }, { - desc: "overflow", - in: FromUint(maxUint64), - wantErr: true, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - got, err := tt.in.Int() - if got != tt.want { - t.Errorf("got: %v, want: %v", got, tt.want) - } - if (err != nil) != tt.wantErr { - t.Errorf("gotErr: %v, wantErr: %v", err, tt.wantErr) - } - }) - } -} - -func TestRangeEqual(t *testing.T) { - tests := []struct { - desc string - inBaseRange YangRange - inTestRange YangRange - want bool - }{{ - desc: "empty range equals empty range", - want: true, - }, { - desc: "test range is default", - inBaseRange: YangRange{R(1, 2)}, want: false, - }, { - desc: "base range is default", - inTestRange: YangRange{R(1, 2)}, want: false, - }, { - desc: "equal ranges", - inBaseRange: YangRange{R(1, 2)}, - inTestRange: YangRange{R(1, 2)}, - want: true, - }, { - desc: "wider base range", - inBaseRange: YangRange{R(1, 3)}, - inTestRange: YangRange{R(1, 2)}, - want: false, - }, { - desc: "equal ranges with multiple subranges", - inBaseRange: YangRange{R(1, 2), R(4, 5)}, - inTestRange: YangRange{R(1, 2), R(4, 5)}, - want: true, - }, { - desc: "multiple subranges with one unequal", - inBaseRange: YangRange{R(1, 2), R(4, 6)}, - inTestRange: YangRange{R(1, 2), R(4, 5)}, - want: false, - }, { - desc: "extra subrange in base range", - inBaseRange: YangRange{R(1, 2)}, - inTestRange: YangRange{R(1, 2), R(4, 5)}, - want: false, - }, { - desc: "extra subrange in test range", - inBaseRange: YangRange{R(1, 2), R(4, 5)}, - inTestRange: YangRange{R(1, 2)}, - want: false, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - if want := tt.inBaseRange.Equal(tt.inTestRange); want != tt.want { - t.Errorf("got %v, want %v", want, tt.want) - } - }) - } -} - -func TestRangeContains(t *testing.T) { - tests := []struct { - desc string - inBaseRange YangRange - inTestRange YangRange - want bool - }{{ - desc: "empty range contained in empty range", - want: true, - }, { - desc: "empty range contained in non-empty range", - inBaseRange: YangRange{R(1, 2)}, - want: true, - }, { - desc: "non-empty range contained in empty range", - inTestRange: YangRange{R(1, 2)}, - want: true, - }, { - desc: "equal ranges contain", - inBaseRange: YangRange{R(1, 2)}, - inTestRange: YangRange{R(1, 2)}, - want: true, - }, { - desc: "superset contains", - inBaseRange: YangRange{R(1, 5)}, - inTestRange: YangRange{R(2, 3)}, - want: true, - }, { - desc: "subset doesn't contain", - inBaseRange: YangRange{R(2, 3)}, - inTestRange: YangRange{R(1, 5)}, - want: false, - }, { - desc: "contain subranges", - inBaseRange: YangRange{R(1, 10)}, - inTestRange: YangRange{R(1, 2), R(4, 5), R(7, 10)}, - want: true, - }, { - desc: "subranges leaks out", - inBaseRange: YangRange{R(1, 10)}, - inTestRange: YangRange{R(1, 2), R(7, 11)}, - want: false, - }, { - desc: "subranges containing a subset", - inBaseRange: YangRange{R(1, 9), R(11, 19), R(21, 29)}, - inTestRange: YangRange{R(23, 25)}, - want: true, - }, { - desc: "subranges containing a single valued range", - inBaseRange: YangRange{R(1, 9), R(11, 19), R(21, 29)}, - inTestRange: YangRange{R(23, 23)}, - want: true, - }, { - desc: "subranges doesn't contain a single outside value", - inBaseRange: YangRange{R(1, 9), R(11, 19), R(21, 29)}, - inTestRange: YangRange{R(20, 20)}, - want: false, - }, { - desc: "smaller range doesn't contain min..max", - inBaseRange: YangRange{R(1, 10)}, - inTestRange: YangRange{R(MinInt64, MaxInt64)}, - want: false, - }, { - desc: "full range contains any", - inBaseRange: YangRange{R(MinInt64, MaxInt64)}, - inTestRange: YangRange{R(1, 10)}, - want: true, - }, { - desc: "smaller range doesn't contain min..a|b..max", - inBaseRange: YangRange{R(1024, 65535)}, - inTestRange: YangRange{R(MinInt64, 4096), R(5120, MaxInt64)}, - want: false, - }, { - desc: "ranges don't overlap with max word used", - inBaseRange: YangRange{R(1024, 65535)}, - inTestRange: YangRange{R(-999999, 4096), R(5120, MaxInt64)}, - want: false, - }, { - desc: "ranges don't overlap with min word used", - inBaseRange: YangRange{R(1024, 65535)}, - inTestRange: YangRange{R(MinInt64, 4096), R(5120, 999999)}, - want: false, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - if got := tt.inBaseRange.Contains(tt.inTestRange); got != tt.want { - t.Errorf("got %v, want %v", got, tt.want) - } - }) - } -} - -func TestParseRangesInt(t *testing.T) { - tests := []struct { - desc string - inParentRange YangRange - in string - want YangRange - wantErrSubstring string - }{{ - desc: "small numbers, coalescing", - in: "0|2..3|4..5", - want: YangRange{R(0, 0), R(2, 5)}, - }, { - desc: "small numbers, out of order, coalescing", - in: "4..5|0|2..3", - want: YangRange{R(0, 0), R(2, 5)}, - }, { - desc: "invalid input: too many ..s", - in: "0|2..3|4..5..6", - wantErrSubstring: "too many '..' in 4..5..6", - }, { - desc: "invalid input: range boundaries out of order", - in: "0|2..3|5..4", - wantErrSubstring: "range boundaries out of order", - }, { - desc: "range with min", - inParentRange: Int64Range, - in: "min..0|2..3|4..5", - want: YangRange{R(MinInt64, 0), R(2, 5)}, - }, { - desc: "range with min but without parent range", - in: "min..0|2..3|4..5", - wantErrSubstring: "empty YangRange parent object", - }, { - desc: "range with max", - inParentRange: Int32Range, - in: "min..0|2..3|4..5|7..max", - want: YangRange{R(minInt32, 0), R(2, 5), R(7, maxInt32)}, - }, { - desc: "coalescing from min to max for uint64", - inParentRange: Uint64Range, - in: "min..0|1..max", - want: YangRange{YRange{FromInt(0), FromUint(maxUint64)}}, - }, { - desc: "coalescing from min to max for uint32", - inParentRange: Uint32Range, - in: "min..0|1..max", - want: YangRange{R(0, maxUint32)}, - }, { - desc: "coalescing from min to max for uint16", - inParentRange: Uint16Range, - in: "min..0|1..max", - want: YangRange{R(0, maxUint16)}, - }, { - desc: "coalescing from min to max for uint8", - inParentRange: Uint8Range, - in: "min..0|1..max", - want: YangRange{R(0, maxUint8)}, - }, { - desc: "coalescing from min to max for int64", - inParentRange: Int64Range, - in: "min..0|1..max", - want: YangRange{R(MinInt64, MaxInt64)}, - }, { - desc: "coalescing from min to max for int32", - inParentRange: Int32Range, - in: "min..0|1..max", - want: YangRange{R(minInt32, maxInt32)}, - }, { - desc: "coalescing from min to max for int16", - inParentRange: Int16Range, - in: "min..0|1..max", - want: YangRange{R(minInt16, maxInt16)}, - }, { - desc: "coalescing from min to max for int8", - inParentRange: Int8Range, - in: "min..0|1..max", - want: YangRange{R(minInt8, maxInt8)}, - }, { - desc: "spelling error", - inParentRange: Int64Range, - in: "mean..0|1..max", - wantErrSubstring: "invalid syntax", - }, { - desc: "big numbers, coalescing", - in: "0..69|4294967294|4294967295", - want: YangRange{R(0, 69), R(4294967294, 4294967295)}, - }, { - desc: "no ranges", - in: "250|500|1000", - want: YangRange{R(250, 250), R(500, 500), R(1000, 1000)}, - }, { - desc: "no ranges unsorted", - in: "1000|500|250", - want: YangRange{R(250, 250), R(500, 500), R(1000, 1000)}, - }, { - desc: "negative numbers", - in: "-31..-1|1..31", - want: YangRange{R(-31, -1), R(1, 31)}, - }, { - desc: "spaces", - in: "-22 | -15 | -7 | 0", - want: YangRange{R(-22, -22), R(-15, -15), R(-7, -7), R(0, 0)}, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - got, err := tt.inParentRange.parseChildRanges(tt.in, false, 0) - if err != nil { - if diff := errdiff.Substring(err, tt.wantErrSubstring); diff != "" { - t.Fatalf("did not get expected error, %s", diff) - } - return - } - - if diff := cmp.Diff(tt.want, got); diff != "" { - t.Errorf("parseChildRanges (-want, +got):\n%s", diff) - } - - if tt.inParentRange == nil { - if got, err = ParseRangesInt(tt.in); err != nil { - t.Fatalf("ParseRangesInt: unexpected error: %v", err) - } - if diff := cmp.Diff(tt.want, got); diff != "" { - t.Errorf("ParseRangesInt (-want, +got):\n%s", diff) - } - } - }) - } -} - -func TestCoalesce(t *testing.T) { - for x, tt := range []struct { - in, out YangRange - }{ - {}, - {YangRange{R(1, 4)}, YangRange{R(1, 4)}}, - {YangRange{R(1, 2), R(3, 4)}, YangRange{R(1, 4)}}, - {YangRange{Rf(10, 25, 1), Rf(30, 40, 1)}, YangRange{Rf(10, 25, 1), Rf(30, 40, 1)}}, - {YangRange{Rf(10, 29, 1), Rf(30, 40, 1)}, YangRange{Rf(10, 40, 1)}}, - {YangRange{R(1, 2), R(2, 4)}, YangRange{R(1, 4)}}, - {YangRange{R(1, 2), R(4, 5)}, YangRange{R(1, 2), R(4, 5)}}, - {YangRange{R(1, 3), R(2, 5)}, YangRange{R(1, 5)}}, - {YangRange{R(1, 10), R(2, 5)}, YangRange{R(1, 10)}}, - {YangRange{R(1, 10), R(1, 2), R(4, 5), R(7, 8)}, YangRange{R(1, 10)}}, - {YangRange{Rf(1, 10, 3), Rf(1, 2, 3), Rf(4, 5, 3), Rf(7, 8, 3)}, YangRange{Rf(1, 10, 3)}}, - } { - out := coalesce(tt.in) - if !out.Equal(tt.out) { - t.Errorf("#%d: got %v, want %v", x, out, tt.out) - } - } -} - -func TestYangRangeSort(t *testing.T) { - for x, tt := range []struct { - in, out YangRange - }{ - {YangRange{}, YangRange{}}, - {YangRange{R(1, 4), R(6, 10)}, YangRange{R(1, 4), R(6, 10)}}, - {YangRange{R(6, 10), R(1, 4)}, YangRange{R(1, 4), R(6, 10)}}, - {YangRange{Rf(10, 25, 1), Rf(30, 40, 1)}, YangRange{Rf(10, 25, 1), Rf(30, 40, 1)}}, - {YangRange{Rf(30, 40, 1), Rf(10, 25, 1)}, YangRange{Rf(10, 25, 1), Rf(30, 40, 1)}}, - {YangRange{R(1, 2)}, YangRange{R(1, 2)}}, - {YangRange{R(1, 2), R(4, 5)}, YangRange{R(1, 2), R(4, 5)}}, - {YangRange{R(1, 3), R(2, 5)}, YangRange{R(1, 3), R(2, 5)}}, - {YangRange{R(1, 10), R(2, 5)}, YangRange{R(1, 10), R(2, 5)}}, - {YangRange{R(1, 10), R(1, 2), R(4, 5), R(7, 8)}, YangRange{R(1, 2), R(1, 10), R(4, 5), R(7, 8)}}, - } { - tt.in.Sort() - if !tt.in.Equal(tt.out) { - t.Errorf("#%d: got %v, want %v", x, tt.in, tt.out) - } - } -} - -func TestParseRangesDecimal(t *testing.T) { - rangeMax := mustParseRangesDecimal("-922337203685477580.8..922337203685477580.7", 1) - rangeRestricted := mustParseRangesDecimal("-42..42|100", 5) - - tests := []struct { - desc string - inParentRange YangRange - in string - inFracDig uint8 - want YangRange - wantErrSubstring string - }{{ - desc: "min..max fraction-digits 1", - inParentRange: rangeMax, - in: "min..max", - inFracDig: 1, - want: YangRange{Rf(MinInt64, MaxInt64, 1)}, - }, { - desc: "min..max fraction-digits 2", - inParentRange: rangeMax, - in: "min..max", - inFracDig: 2, - want: YangRange{Rf(MinInt64, MaxInt64, 2)}, - }, { - desc: "min..max no parent range", - in: "min..max", - inFracDig: 2, - want: YangRange{Rf(MinInt64, MaxInt64, 2)}, - wantErrSubstring: "empty YangRange parent object", - }, { - desc: "min..max on fragmented range", - inParentRange: rangeRestricted, - in: "min..max", - inFracDig: 5, - wantErrSubstring: "not within", - }, { - desc: "small decimals", - inParentRange: rangeMax, - in: "0.0|2.0..30.0|1.34..1.99", - inFracDig: 2, - want: YangRange{Rf(0, 0, 2), Rf(134, 3000, 2)}, - }, { - desc: "small decimals on restricted range", - inParentRange: rangeRestricted, - in: "0.0|2.0..30.0|1.34..1.99999", - inFracDig: 5, - want: YangRange{Rf(0, 0, 5), Rf(134000, 3000000, 5)}, - }, { - desc: "small decimals with coalescing", - inParentRange: rangeMax, - in: "0.0|2.0..30.0", - inFracDig: 1, - want: YangRange{Rf(0, 0, 1), Rf(20, 300, 1)}, - }, { - desc: "fractional digit cannot be too high", - in: "0.0|2.0..30.0", - inFracDig: 19, - wantErrSubstring: "invalid number of fraction digits", - }, { - desc: "fractional digit cannot be 0", - in: "0.0|2.0..30.0", - inFracDig: 0, - wantErrSubstring: "invalid number of fraction digits", - }, { - desc: "big decimals", - in: "0.0..69|4294967294.1234|4294967295.1234", - inFracDig: 4, - want: YangRange{Rf(0, 690000, 4), Rf(42949672941234, 42949672941234, 4), Rf(42949672951234, 42949672951234, 4)}, - }, { - desc: "small decimals, out of order", - in: "4.0..5.55|0|2.32..3.23", - inFracDig: 3, - want: YangRange{Rf(0, 0, 3), Rf(2320, 3230, 3), Rf(4000, 5550, 3)}, - }, { - desc: "invalid input: too many ..s", - in: "4.0..5.55..6.66|0|2.32..3.23", - inFracDig: 3, - wantErrSubstring: "too many '..'", - }, { - desc: "invalid input: range boundaries out of order", - in: "5..4.0|0|2.32..3.23", - inFracDig: 3, - wantErrSubstring: "range boundaries out of order", - }, { - desc: "range with min", - inParentRange: rangeMax, - in: "4.0..5.55|min..0|2.32..3.23", - inFracDig: 3, - want: YangRange{Rf(MinInt64, 0, 3), Rf(2320, 3230, 3), Rf(4000, 5550, 3)}, - }, { - desc: "range with max", - inParentRange: rangeMax, - in: "4.0..max|min..0|2.32..3.23", - inFracDig: 3, - want: YangRange{Rf(MinInt64, 0, 3), Rf(2320, 3230, 3), Rf(4000, MaxInt64, 3)}, - }, { - desc: "coalescing from min to max", - inParentRange: rangeMax, - in: "min..0.9|1..max", - inFracDig: 1, - want: YangRange{Rf(MinInt64, MaxInt64, 1)}, - }, { - desc: "spelling error", - inParentRange: rangeMax, - in: "min..0.9|1..masks", - inFracDig: 1, - wantErrSubstring: "invalid syntax", - }, { - desc: "no ranges", - in: "250.55|500.0|1000", - inFracDig: 2, - want: YangRange{Rf(25055, 25055, 2), Rf(50000, 50000, 2), Rf(100000, 100000, 2)}, - }, { - desc: "no ranges unsorted", - in: "1000|500.0|250.55", - inFracDig: 2, - want: YangRange{Rf(25055, 25055, 2), Rf(50000, 50000, 2), Rf(100000, 100000, 2)}, - }, { - desc: "negative decimals", - in: "-31.2..-1.5|1.5..31.2", - inFracDig: 1, - want: YangRange{Rf(-312, -15, 1), Rf(15, 312, 1)}, - }, { - desc: "spaces", - in: "-22.5 | -15 | -7.5 | 0", - inFracDig: 1, - want: YangRange{Rf(-225, -225, 1), Rf(-150, -150, 1), Rf(-75, -75, 1), Rf(0, 0, 1)}, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - got, err := tt.inParentRange.parseChildRanges(tt.in, true, tt.inFracDig) - if err != nil { - if diff := errdiff.Substring(err, tt.wantErrSubstring); diff != "" { - t.Fatalf("did not get expected error, %s", diff) - } - return - } - - if diff := cmp.Diff(tt.want, got); diff != "" { - t.Errorf("(-want, +got):\n%s", diff) - } - - if tt.inParentRange == nil { - if got, err = ParseRangesDecimal(tt.in, tt.inFracDig); err != nil { - t.Fatalf("ParseRangesDecimal: unexpected error: %v", err) - } - if diff := cmp.Diff(tt.want, got); diff != "" { - t.Errorf("ParseRangesDecimal (-want, +got):\n%s", diff) - } - } - }) - } -} - -func TestAdd(t *testing.T) { - tests := []struct { - desc string - inVal Number - inAdd uint64 - want Number - }{{ - desc: "add one to integer", - inVal: FromInt(1), - inAdd: 1, - want: FromInt(2), - }, { - desc: "add one to decimal64", - inVal: FromFloat(1.0), - inAdd: 1, - want: FromFloat(1.1), - }, { - desc: "negative int becomes positive", - inVal: FromInt(-2), - inAdd: 3, - want: FromInt(1), - }, { - desc: "negative int stays negative", - inVal: FromInt(-3), - inAdd: 1, - want: FromInt(-2), - }, { - desc: "negative decimal becomes positive", - inVal: FromFloat(-2), - inAdd: 35, - want: FromFloat(1.5), - }, { - desc: "negative decimal stays negative", - inVal: FromFloat(-42.22), - inAdd: 4122, - want: FromFloat(-1.0), - }, { - desc: "explicitly set fraction digits", - inVal: Number{Value: 10000, FractionDigits: 5}, - inAdd: 1, - want: Number{Value: 10001, FractionDigits: 5}, - }, { - desc: "explicitly set fraction digits - negative", - inVal: Number{Value: 0, FractionDigits: 3}, - inAdd: 42, - want: FromFloat(0.042), - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - got := tt.inVal.addQuantum(tt.inAdd) - if !cmp.Equal(got, tt.want) { - t.Fatalf("did get expected result, got: %s, want: %s", got.String(), tt.want.String()) - } - }) - } -} - -func TestParseInt(t *testing.T) { - tests := []struct { - desc string - inStr string - want Number - wantErrSubstring string - }{{ - desc: "invalid string supplied", - inStr: "fish", - wantErrSubstring: "valid syntax", - }, { - desc: "negative int", - inStr: "-42", - want: FromInt(-42), - }, { - desc: "positive int", - inStr: "42", - want: FromInt(42), - }, { - desc: "positive int with plus sign", - inStr: "+42", - want: FromInt(42), - }, { - desc: "zero", - inStr: "0", - want: FromInt(0), - }, { - desc: "min", - inStr: "min", - wantErrSubstring: "invalid syntax", - }, { - desc: "max", - inStr: "max", - wantErrSubstring: "invalid syntax", - }, { - desc: "just a sign", - inStr: "-", - wantErrSubstring: "sign with no value", - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - got, err := ParseInt(tt.inStr) - if err != nil { - if diff := errdiff.Substring(err, tt.wantErrSubstring); diff != "" { - t.Fatalf("did not get expected error, %s", diff) - } - return - } - - if !cmp.Equal(got, tt.want) { - t.Errorf("did not get expected Number, got: %s, want: %s", got, tt.want) - } - - if got.IsDecimal() { - t.Errorf("Got decimal value instead of int: %v", got) - } - }) - } -} - -func TestParseDecimal(t *testing.T) { - tests := []struct { - desc string - inStr string - inFracDig uint8 - skipFractionDigitsCheck bool - want Number - wantErrSubstring string - }{{ - desc: "too few fractional digits", - inStr: "1.000", - inFracDig: 0, - wantErrSubstring: "invalid number of fraction digits", - }, { - desc: "too many fraction digits", - inStr: "1.000", - inFracDig: 24, - wantErrSubstring: "invalid number of fraction digits", - }, { - desc: "more digits supplied", - inStr: "1.14242", - inFracDig: 2, - wantErrSubstring: "has too much precision", - }, { - desc: "single digit precision", - inStr: "1.1", - inFracDig: 1, - want: Number{Value: 11, FractionDigits: 1}, - }, { - desc: "max precision", - inStr: "0.100000000000000000", - inFracDig: 18, - skipFractionDigitsCheck: true, - want: FromFloat(0.1), - }, { - desc: "max precision but not supplied", - inStr: "0.1", - inFracDig: 4, - skipFractionDigitsCheck: true, - want: FromFloat(0.1), - }, { - desc: "invalid string supplied", - inStr: "fish", - inFracDig: 17, - wantErrSubstring: "not a valid decimal number", - }, { - desc: "negative number", - inStr: "-42.0", - inFracDig: 1, - want: FromFloat(-42), - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - got, err := ParseDecimal(tt.inStr, tt.inFracDig) - if err != nil { - if diff := errdiff.Substring(err, tt.wantErrSubstring); diff != "" { - t.Fatalf("did not get expected error, %s", diff) - } - return - } - - if !cmp.Equal(got, tt.want) { - t.Errorf("did not get expected Number, got: %s, want: %s", got, tt.want) - } - - if !tt.skipFractionDigitsCheck { - if got, want := got.FractionDigits, tt.want.FractionDigits; got != want { - t.Errorf("fractional digits not equal, got: %d, want: %d", got, want) - } - } - - if !got.IsDecimal() { - t.Errorf("Got non-decimal value: %v", got) - } - }) - } -} - -func TestNumberString(t *testing.T) { - tests := []struct { - desc string - in Number - want string - }{{ - desc: "min", - in: FromInt(MinInt64), - want: "-9223372036854775808", - }, { - desc: "max", - in: FromInt(MaxInt64), - want: "9223372036854775807", - }, { - desc: "integer", - in: Number{Value: 1}, - want: "1", - }, { - desc: "negative integer", - in: Number{Value: 1, Negative: true}, - want: "-1", - }, { - desc: "decimal, fractional digits = 1", - in: Number{Value: 1, FractionDigits: 1}, - want: "0.1", - }, { - desc: "decimal, fractional digits = 18", - in: Number{Value: 123456789012345678, FractionDigits: 18}, - want: "0.123456789012345678", - }, { - desc: "negative decimal", - in: Number{Value: 100, FractionDigits: 2, Negative: true}, - want: "-1.00", - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - if got := tt.in.String(); got != tt.want { - t.Fatalf("did not get expected number, got: %s, want: %s", got, tt.want) - } - }) - } -} - -func TestEnumToJson(t *testing.T) { - tests := []struct { - desc string - in *EnumType - want string - wantErr bool - }{{ - "empty enum to JSON", - &EnumType{ - last: -1, // +1 will start at 0 - min: 0, - max: MaxBitfieldSize - 1, - ToString: map[int64]string{}, - ToInt: map[string]int64{}, - }, - `{}`, - false, - }, { - "2 value enum to JSON", - &EnumType{ - last: 2, - min: 0, - max: MaxBitfieldSize - 1, - ToString: map[int64]string{ - 1: "value1", - 2: "value2", - }, - ToInt: map[string]int64{ - "value1": 1, - "value2": 2, - }, - }, - `{"ToString":{"1":"value1","2":"value2"},"ToInt":{"value1":1,"value2":2}}`, - false, - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - got, err := json.Marshal(tt.in) - if string(got) != tt.want { - t.Errorf("got: %v, want: %v", string(got), tt.want) - } - if (err != nil) != tt.wantErr { - t.Errorf("gotErr: %v, wantErr: %v", err, tt.wantErr) - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yang/types_test.go b/src/webui/internal/goyang/pkg/yang/types_test.go deleted file mode 100644 index d66a4f43f..000000000 --- a/src/webui/internal/goyang/pkg/yang/types_test.go +++ /dev/null @@ -1,1720 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/openconfig/gnmi/errdiff" -) - -func TestTypeResolve(t *testing.T) { - tests := []struct { - desc string - in *Type - err string - out *YangType - }{{ - desc: "basic int64", - in: &Type{ - Name: "int64", - }, - out: &YangType{ - Name: "int64", - Kind: Yint64, - Range: Int64Range, - }, - }, { - desc: "basic int64 with a range", - in: &Type{ - Name: "int64", - Range: &Range{Name: "-42..42"}, - }, - out: &YangType{ - Name: "int64", - Kind: Yint64, - Range: YangRange{{Min: FromInt(-42), Max: FromInt(42)}}, - }, - }, { - desc: "basic uint64 with an invalid range", - in: &Type{ - Name: "uint64", - Range: &Range{Name: "-42..42"}, - }, - err: "unknown: bad range: -42..42 not within 0..18446744073709551615", - }, { - desc: "basic uint64 with an unparseable range", - in: &Type{ - Name: "uint64", - Range: &Range{Name: "-42..forty-two"}, - }, - err: `unknown: bad range: strconv.ParseUint: parsing "forty-two": invalid syntax`, - }, { - desc: "basic string with a length", - in: &Type{ - Name: "string", - Length: &Length{Name: "24..42"}, - }, - out: &YangType{ - Name: "string", - Kind: Ystring, - Length: YangRange{{Min: FromInt(24), Max: FromInt(42)}}, - }, - }, { - desc: "basic string with an invalid range", - in: &Type{ - Name: "string", - Length: &Length{Name: "-42..42"}, - }, - err: `unknown: bad length: -42..42 not within 0..18446744073709551615`, - }, { - desc: "basic binary with a length", - in: &Type{ - Name: "binary", - Length: &Length{Name: "24..42"}, - }, - out: &YangType{ - Name: "binary", - Kind: Ybinary, - Length: YangRange{{Min: FromInt(24), Max: FromInt(42)}}, - }, - }, { - desc: "basic binary with an unparseable range", - in: &Type{ - Name: "binary", - Length: &Length{Name: "42..forty-two"}, - }, - err: `unknown: bad length: strconv.ParseUint: parsing "forty-two": invalid syntax`, - }, { - desc: "invalid fraction-digits argument for boolean value", - in: &Type{ - Name: "boolean", - FractionDigits: &Value{Name: "42"}, - }, - err: "unknown: fraction-digits only allowed for decimal64 values", - }, { - desc: "required field fraction-digits not supplied for decimal64", - in: &Type{ - Name: "decimal64", - }, - err: "unknown: value is required in the range of [1..18]", - }, { - desc: "invalid identityref that doesn't have a base identity name", - in: &Type{ - Name: "identityref", - }, - err: "unknown: an identityref must specify a base", - }, { - desc: "invalid decimal64 having an invalid fraction-digits value", - in: &Type{ - Name: "decimal64", - FractionDigits: &Value{Name: "42"}, - }, - err: "unknown: value 42 out of range [1..18]", - }, { - desc: "decimal64", - in: &Type{ - Name: "decimal64", - FractionDigits: &Value{Name: "7"}, - }, - out: &YangType{ - Name: "decimal64", - Kind: Ydecimal64, - FractionDigits: 7, - Range: YangRange{Rf(MinInt64, MaxInt64, 7)}, - }, - }, { - desc: "instance-identifier with unspecified require-instance value (default true)", - in: &Type{ - Name: "instance-identifier", - RequireInstance: nil, - }, - out: &YangType{ - Name: "instance-identifier", - Kind: YinstanceIdentifier, - // https://tools.ietf.org/html/rfc7950#section-9.9.3 - // require-instance defaults to true. - OptionalInstance: false, - }, - }, { - desc: "instance-identifier with true require-instance value", - in: &Type{ - Name: "instance-identifier", - RequireInstance: &Value{Name: "true"}, - }, - out: &YangType{ - Name: "instance-identifier", - Kind: YinstanceIdentifier, - OptionalInstance: false, - }, - }, { - desc: "instance-identifier with false require-instance value", - in: &Type{ - Name: "instance-identifier", - RequireInstance: &Value{Name: "false"}, - }, - out: &YangType{ - Name: "instance-identifier", - Kind: YinstanceIdentifier, - OptionalInstance: true, - }, - }, { - desc: "instance-identifier with invalid require-instance value", - in: &Type{ - Name: "instance-identifier", - RequireInstance: &Value{Name: "foo"}, - }, - err: "invalid boolean: foo", - }, { - desc: "enum with unspecified values", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY"}, - {Name: "VENUS"}, - {Name: "EARTH"}, - }, - }, - out: &YangType{ - Name: "enumeration", - Kind: Yenum, - Enum: &EnumType{ - last: 2, - min: MinEnum, - max: MaxEnum, - unique: true, - ToString: map[int64]string{ - 0: "MERCURY", - 1: "VENUS", - 2: "EARTH", - }, - ToInt: map[string]int64{ - "MERCURY": 0, - "VENUS": 1, - "EARTH": 2, - }, - }, - }, - }, { - desc: "enum with specified values", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "-1"}}, - {Name: "VENUS", Value: &Value{Name: "10"}}, - {Name: "EARTH", Value: &Value{Name: "30"}}, - }, - }, - out: &YangType{ - Name: "enumeration", - Kind: Yenum, - Enum: &EnumType{ - last: 30, - min: MinEnum, - max: MaxEnum, - unique: true, - ToString: map[int64]string{ - -1: "MERCURY", - 10: "VENUS", - 30: "EARTH", - }, - ToInt: map[string]int64{ - "MERCURY": -1, - "VENUS": 10, - "EARTH": 30, - }, - }, - }, - }, { - desc: "enum with some values specified", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "-1"}}, - {Name: "VENUS", Value: &Value{Name: "10"}}, - {Name: "EARTH"}, - }, - }, - out: &YangType{ - Name: "enumeration", - Kind: Yenum, - Enum: &EnumType{ - last: 11, - min: MinEnum, - max: MaxEnum, - unique: true, - ToString: map[int64]string{ - -1: "MERCURY", - 10: "VENUS", - 11: "EARTH", - }, - ToInt: map[string]int64{ - "MERCURY": -1, - "VENUS": 10, - "EARTH": 11, - }, - }, - }, - }, { - desc: "enum with repeated specified values", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "1"}}, - {Name: "VENUS", Value: &Value{Name: "10"}}, - {Name: "EARTH", Value: &Value{Name: "1"}}, - }, - }, - err: "unknown: fields EARTH and MERCURY conflict on value 1", - }, { - desc: "enum with repeated specified names", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "-1"}}, - {Name: "VENUS", Value: &Value{Name: "10"}}, - {Name: "MERCURY", Value: &Value{Name: "30"}}, - }, - }, - err: "unknown: field MERCURY already assigned", - }, { - desc: "enum with last specified value equal to the max enum value", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "-2147483648"}}, - {Name: "VENUS", Value: &Value{Name: "2147483647"}}, - {Name: "EARTH"}, - }, - }, - err: `unknown: enum "EARTH" must specify a value since previous enum is the maximum value allowed`, - }, { - desc: "enum value too small", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "-2147483649"}}, - {Name: "VENUS", Value: &Value{Name: "0"}}, - {Name: "EARTH"}, - }, - }, - err: `unknown: value -2147483649 for MERCURY too small (minimum is -2147483648)`, - }, { - desc: "enum value too large", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "-2147483648"}}, - {Name: "VENUS", Value: &Value{Name: "2147483648"}}, - {Name: "EARTH"}, - }, - }, - err: `unknown: value 2147483648 for VENUS too large (maximum is 2147483647)`, - }, { - desc: "enum with an unparseable value", - in: &Type{ - Name: "enumeration", - Enum: []*Enum{ - {Name: "MERCURY", Value: &Value{Name: "-1"}}, - {Name: "VENUS", Value: &Value{Name: "10"}}, - {Name: "EARTH", Value: &Value{Name: "five"}}, - }, - }, - err: `unknown: strconv.ParseUint: parsing "five": invalid syntax`, - // TODO(borman): Add in more tests as we honor more fields - // in Type. - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - // We can initialize a value to ourself, so to it here. - errs := tt.in.resolve(newTypeDictionary()) - - // TODO(borman): Do not hack out Root and Base. These - // are hacked out for now because they can be self-referential, - // making construction of them difficult. - tt.in.YangType.Root = nil - tt.in.YangType.Base = nil - - switch { - case tt.err == "" && len(errs) > 0: - t.Fatalf("unexpected errors: %v", errs) - case tt.err != "" && len(errs) == 0: - t.Fatalf("did not get expected errors: %v", tt.err) - case len(errs) > 1: - t.Fatalf("too many errors: %v", errs) - case len(errs) == 1 && errs[0].Error() != tt.err: - t.Fatalf("got error %v, want %s", errs[0], tt.err) - case len(errs) != 0: - return - } - - if diff := cmp.Diff(tt.in.YangType, tt.out); diff != "" { - t.Errorf("YangType (-got, +want):\n%s", diff) - } - }) - } -} - -func TestTypedefResolve(t *testing.T) { - tests := []struct { - desc string - in *Typedef - err string - out *YangType - }{{ - desc: "basic int64", - in: &Typedef{ - Name: "time", - Parent: baseTypes["int64"].typedef(), - Default: &Value{Name: "42"}, - Type: &Type{ - Name: "int64", - }, - Units: &Value{Name: "nanoseconds"}, - }, - out: &YangType{ - Name: "time", - Kind: Yint64, - Base: &Type{ - Name: "int64", - }, - Units: "nanoseconds", - Default: "42", - HasDefault: true, - Range: Int64Range, - }, - }, { - desc: "uint32 with more specific range", - in: &Typedef{ - Name: "another-counter", - Parent: &Typedef{ - Name: "counter", - Parent: baseTypes["uint32"].typedef(), - Type: &Type{ - Name: "uint32", - Range: &Range{Name: "0..42"}, - }, - }, - Type: &Type{ - Name: "uint32", - Range: &Range{Name: "10..20"}, - }, - }, - out: &YangType{ - Name: "another-counter", - Kind: Yuint32, - Base: &Type{ - Name: "uint32", - }, - Range: YangRange{{Min: FromInt(10), Max: FromInt(20)}}, - }, - // TODO(wenovus): Add tests on range and length inheritance once those are fixed. - }} - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - // We can initialize a value to ourself, so to it here. - errs := tt.in.resolve(newTypeDictionary()) - - switch { - case tt.err == "" && len(errs) > 0: - t.Fatalf("unexpected errors: %v", errs) - case tt.err != "" && len(errs) == 0: - t.Fatalf("did not get expected errors: %v", tt.err) - case len(errs) > 1: - t.Fatalf("too many errors: %v", errs) - case len(errs) == 1 && errs[0].Error() != tt.err: - t.Fatalf("got error %v, want %s", errs[0], tt.err) - case len(errs) != 0: - return - } - - if diff := cmp.Diff(tt.in.YangType, tt.out); diff != "" { - t.Errorf("YangType (-got, +want):\n%s", diff) - } - }) - } -} - -func TestTypeResolveUnions(t *testing.T) { - tests := []struct { - desc string - leafNode string - wantType *testEnumTypeStruct - wantErrSubstr string - }{{ - desc: "simple union", - leafNode: ` - typedef alpha { - type union { - type string; - type uint32; - type enumeration { - enum zero; - enum one; - enum seven { - value 7; - } - } - } - } - - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testEnumTypeStruct{ - Name: "alpha", - Type: []*testEnumTypeStruct{{ - Name: "string", - }, { - Name: "uint32", - }, { - Name: "enumeration", - ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, - }}, - }, - }, { - desc: "union with typedef", - leafNode: ` - typedef alpha { - type union { - type string; - type uint32; - type enumeration { - enum zero; - enum one; - enum seven { - value 7; - } - } - type bravo; - } - } - - typedef bravo { - type union { - type uint8; - type uint16; - type enumeration { - enum two { - value 2; - } - enum three; - enum four; - } - } - } - - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testEnumTypeStruct{ - Name: "alpha", - Type: []*testEnumTypeStruct{{ - Name: "string", - }, { - Name: "uint32", - }, { - Name: "enumeration", - ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, - }, { - Name: "bravo", - Type: []*testEnumTypeStruct{{ - Name: "uint8", - }, { - Name: "uint16", - }, { - Name: "enumeration", - ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, - }}, - }}, - }, - }, { - desc: "nested unions with typedef", - leafNode: ` - typedef alpha { - type union { - type union { - type uint32; - type string; - type enumeration { - enum zero; - enum one; - enum seven { - value 7; - } - } - } - type bravo; - } - } - - typedef bravo { - type union { - type uint8; - type uint16; - type enumeration { - enum two { - value 2; - } - enum three; - enum four; - } - } - } - - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testEnumTypeStruct{ - Name: "alpha", - Type: []*testEnumTypeStruct{{ - Name: "union", - Type: []*testEnumTypeStruct{{ - Name: "uint32", - }, { - Name: "string", - }, { - Name: "enumeration", - ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, - }}, - }, { - Name: "bravo", - Type: []*testEnumTypeStruct{{ - Name: "uint8", - }, { - Name: "uint16", - }, { - Name: "enumeration", - ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, - }}, - }}, - }, - }, { - desc: "simple union with multiple enumerations", - leafNode: ` - leaf test-leaf { - type union { - type string; - type uint32; - type enumeration { - enum zero; - enum one; - enum seven { - value 7; - } - } - type enumeration { - enum two { - value 2; - } - enum three; - enum four; - } - } - } - } // end module`, - wantType: &testEnumTypeStruct{ - Name: "union", - Type: []*testEnumTypeStruct{{ - Name: "string", - }, { - Name: "uint32", - }, { - Name: "enumeration", - ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, - }, { - Name: "enumeration", - ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, - }}, - }, - }, { - desc: "typedef union with multiple enumerations", - leafNode: ` - typedef alpha { - type union { - type string; - type uint32; - type enumeration { - enum zero; - enum one; - enum seven { - value 7; - } - } - type enumeration { - enum two { - value 2; - } - enum three; - enum four; - } - } - } - - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testEnumTypeStruct{ - Name: "alpha", - Type: []*testEnumTypeStruct{{ - Name: "string", - }, { - Name: "uint32", - }, { - Name: "enumeration", - ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, - }, { - Name: "enumeration", - ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, - }}, - }, - }, { - desc: "simple union containing typedef union, both with enumerations", - leafNode: ` - typedef alpha { - type union { - type string; - type uint32; - type enumeration { - enum zero; - enum one; - enum seven { - value 7; - } - } - } - } - - leaf test-leaf { - type union { - type alpha; - type enumeration { - enum two { - value 2; - } - enum three; - enum four; - } - } - } - } // end module`, - wantType: &testEnumTypeStruct{ - Name: "union", - Type: []*testEnumTypeStruct{{ - Name: "alpha", - Type: []*testEnumTypeStruct{{ - Name: "string", - }, { - Name: "uint32", - }, { - Name: "enumeration", - ToInt: map[string]int64{"one": 1, "seven": 7, "zero": 0}, - }}, - }, { - Name: "enumeration", - ToInt: map[string]int64{"two": 2, "three": 3, "four": 4}, - }}, - }, - }, { - desc: "simple union containing typedef union containing another typedef union, all with multiple simple and typedef enumerations", - leafNode: ` - typedef a { - type enumeration { - enum un { - value 1; - } - enum deux; - } - } - - typedef b { - type enumeration { - enum trois { - value 3; - } - enum quatre; - } - } - - typedef c { - type enumeration { - enum cinq { - value 5; - } - enum sept { - value 7; - } - } - } - - typedef d { - type enumeration { - enum huit { - value 8; - } - enum neuf; - } - } - - typedef e { - type enumeration { - enum dix { - value 10; - } - enum onze; - } - } - - typedef f { - type enumeration { - enum douze { - value 12; - } - enum treize; - } - } - - typedef bravo { - type union { - type uint32; - type enumeration { - enum eight { - value 8; - } - enum nine; - } - type enumeration { - enum ten { - value 10; - } - enum eleven; - } - type e; - type f; - } - } - - typedef alpha { - type union { - type uint16; - type enumeration { - enum four { - value 4; - } - enum five; - } - type enumeration { - enum six { - value 6; - } - enum seven; - } - type c; - type d; - type bravo; - } - } - - leaf test-leaf { - type union { - type uint8; - type enumeration { - enum zero; - enum one; - } - type enumeration { - enum two { - value 2; - } - enum three; - } - type a; - type b; - type alpha; - } - } - } // end module`, - wantType: &testEnumTypeStruct{ - Name: "union", - Type: []*testEnumTypeStruct{{ - Name: "uint8", - }, { - Name: "enumeration", - ToInt: map[string]int64{"zero": 0, "one": 1}, - }, { - Name: "enumeration", - ToInt: map[string]int64{"two": 2, "three": 3}, - }, { - Name: "a", - ToInt: map[string]int64{"un": 1, "deux": 2}, - }, { - Name: "b", - ToInt: map[string]int64{"trois": 3, "quatre": 4}, - }, { - Name: "alpha", - Type: []*testEnumTypeStruct{{ - Name: "uint16", - }, { - Name: "enumeration", - ToInt: map[string]int64{"four": 4, "five": 5}, - }, { - Name: "enumeration", - ToInt: map[string]int64{"six": 6, "seven": 7}, - }, { - Name: "c", - ToInt: map[string]int64{"cinq": 5, "sept": 7}, - }, { - Name: "d", - ToInt: map[string]int64{"huit": 8, "neuf": 9}, - }, { - Name: "bravo", - Type: []*testEnumTypeStruct{{ - Name: "uint32", - }, { - Name: "enumeration", - ToInt: map[string]int64{"eight": 8, "nine": 9}, - }, { - Name: "enumeration", - ToInt: map[string]int64{"ten": 10, "eleven": 11}, - }, { - Name: "e", - ToInt: map[string]int64{"dix": 10, "onze": 11}, - }, { - Name: "f", - ToInt: map[string]int64{"douze": 12, "treize": 13}, - }}, - }}, - }}, - }, - }} - - getTestLeaf := func(ms *Modules) (*YangType, error) { - const module = "test" - m, ok := ms.Modules[module] - if !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - if len(m.Leaf) == 0 { - return nil, fmt.Errorf("node %v is missing imports", m) - } - e := ToEntry(m) - return e.Dir["test-leaf"].Type, nil - } - - for _, tt := range tests { - inModules := map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - ` + tt.leafNode, - } - - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - for n, m := range inModules { - if err := ms.Parse(m, n); err != nil { - t.Fatalf("error parsing module %s, got: %v, want: nil", n, err) - } - } - errs := ms.Process() - var err error - if len(errs) > 1 { - t.Fatalf("Got more than 1 error: %v", errs) - } else if len(errs) == 1 { - err = errs[0] - } - if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Errorf("Did not get expected error: %s", diff) - } - if err != nil { - return - } - - gotType, err := getTestLeaf(ms) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(filterTypeNames(gotType), tt.wantType); diff != "" { - t.Errorf("Type.resolve() union types test (-got, +want):\n%s", diff) - } - }) - } -} - -type testEnumTypeStruct struct { - Name string - // ToInt is the ToInt map representing the enum value (if present). - ToInt map[string]int64 - Type []*testEnumTypeStruct -} - -// filterTypeNames returns a testEnumTypeStruct with only the -// YangType.Name fields of the given type, preserving -// the recursive structure of the type, to work around cmp not -// having an allowlist way of specifying which fields to -// compare and YangType having a custom Equal function. -func filterTypeNames(ytype *YangType) *testEnumTypeStruct { - filteredNames := &testEnumTypeStruct{Name: ytype.Name} - if ytype.Enum != nil { - filteredNames.ToInt = ytype.Enum.ToInt - } - for _, subtype := range ytype.Type { - filteredNames.Type = append(filteredNames.Type, filterTypeNames(subtype)) - } - return filteredNames -} - -func TestPattern(t *testing.T) { - tests := []struct { - desc string - leafNode string - wantType *YangType - wantErrSubstr string - }{{ - desc: "Only normal patterns", - leafNode: ` - leaf test-leaf { - type string { - o:bar 'coo'; - o:bar 'foo'; - pattern 'charlie'; - o:bar 'goo'; - } - } - } // end module`, - wantType: &YangType{ - Pattern: []string{"charlie"}, - }, - }, { - desc: "Only posix patterns", - leafNode: ` - leaf test-leaf { - type string { - o:bar 'coo'; - o:posix-pattern 'bravo'; - o:bar 'foo'; - o:posix-pattern 'charlie'; - o:bar 'goo'; - } - } - } // end module`, - wantType: &YangType{ - POSIXPattern: []string{"bravo", "charlie"}, - }, - }, { - desc: "No patterns", - leafNode: ` - leaf test-leaf { - type string; - } - }`, - wantType: &YangType{ - Pattern: nil, - POSIXPattern: nil, - }, - }, { - desc: "Both patterns", - leafNode: ` - leaf test-leaf { - type string { - pattern 'alpha'; - o:posix-pattern 'bravo'; - o:posix-pattern 'charlie'; - o:bar 'coo'; - o:posix-pattern 'delta'; - } - } - } // end module`, - wantType: &YangType{ - Pattern: []string{"alpha"}, - POSIXPattern: []string{"bravo", "charlie", "delta"}, - }, - }, { - desc: "Both patterns, but with non-openconfig-extensions pretenders", - leafNode: ` - leaf test-leaf { - type string { - pattern 'alpha'; - o:bar 'coo'; - o:posix-pattern 'delta'; - - n:posix-pattern 'golf'; - - pattern 'bravo'; - o:bar 'foo'; - o:posix-pattern 'echo'; - - pattern 'charlie'; - o:bar 'goo'; - o:posix-pattern 'foxtrot'; - - n:posix-pattern 'hotel'; - } - } - } // end module`, - wantType: &YangType{ - Pattern: []string{"alpha", "bravo", "charlie"}, - POSIXPattern: []string{"delta", "echo", "foxtrot"}, - }, - }, { - desc: "Union type", - leafNode: ` - leaf test-leaf { - type union { - type string { - pattern 'alpha'; - o:bar 'coo'; - o:posix-pattern 'delta'; - - pattern 'bravo'; - o:bar 'foo'; - o:posix-pattern 'echo'; - n:posix-pattern 'echo2'; - - pattern 'charlie'; - o:bar 'goo'; - o:posix-pattern 'foxtrot'; - } - type uint64; - } - } - } // end module`, - wantType: &YangType{ - Type: []*YangType{{ - Pattern: []string{"alpha", "bravo", "charlie"}, - POSIXPattern: []string{"delta", "echo", "foxtrot"}, - }, { - Pattern: nil, - POSIXPattern: nil, - }}, - }, - }, { - desc: "Union type -- de-duping string types", - leafNode: ` - leaf test-leaf { - type union { - type string { - pattern 'alpha'; - o:posix-pattern 'alpha'; - } - type string { - pattern 'alpha'; - o:posix-pattern 'alpha'; - } - } - } - } // end module`, - wantType: &YangType{ - Type: []*YangType{{ - Pattern: []string{"alpha"}, - POSIXPattern: []string{"alpha"}, - }}, - }, - }, { - desc: "Union type -- different string types due to different patterns", - leafNode: ` - leaf test-leaf { - type union { - type string { - pattern 'alpha'; - } - type string { - pattern 'bravo'; - } - } - } - } // end module`, - wantType: &YangType{ - Type: []*YangType{{ - Pattern: []string{"alpha"}, - }, { - Pattern: []string{"bravo"}, - }}, - }, - }, { - desc: "Union type -- different string types due to different posix-patterns", - leafNode: ` - leaf test-leaf { - type union { - type string { - o:posix-pattern 'alpha'; - } - type string { - o:posix-pattern 'bravo'; - } - } - } - } // end module`, - wantType: &YangType{ - Type: []*YangType{{ - POSIXPattern: []string{"alpha"}, - }, { - POSIXPattern: []string{"bravo"}, - }}, - }, - }, { - desc: "typedef", - leafNode: ` - leaf test-leaf { - type leaf-type; - } - - typedef leaf-type { - type string { - pattern 'alpha'; - o:bar 'coo'; - o:posix-pattern 'delta'; - - pattern 'bravo'; - o:bar 'foo'; - o:posix-pattern 'echo'; - - pattern 'charlie'; - o:bar 'goo'; - o:posix-pattern 'foxtrot'; - } - } - } // end module`, - wantType: &YangType{ - Pattern: []string{"alpha", "bravo", "charlie"}, - POSIXPattern: []string{"delta", "echo", "foxtrot"}, - }, - }, { - desc: "invalid POSIX pattern", - leafNode: ` - leaf test-leaf { - type leaf-type; - } - - typedef leaf-type { - type string { - o:posix-pattern '?'; - } - } - } // end module`, - wantErrSubstr: "bad pattern", - }} - - getTestLeaf := func(ms *Modules) (*YangType, error) { - const module = "test" - m, ok := ms.Modules[module] - if !ok { - return nil, fmt.Errorf("can't find module %q", module) - } - - if len(m.Leaf) == 0 { - return nil, fmt.Errorf("node %v is missing imports", m) - } - e := ToEntry(m) - return e.Dir["test-leaf"].Type, nil - } - - for _, tt := range tests { - inModules := map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - - import non-openconfig-extensions { - prefix "n"; - description "non-openconfig-extensions module"; - } - import openconfig-extensions { - prefix "o"; - description "openconfig-extensions module"; - }` + tt.leafNode, - "openconfig-extensions": ` - module openconfig-extensions { - prefix "o"; - namespace "urn:o"; - - extension bar { - argument "baz"; - } - - extension posix-pattern { - argument "pattern"; - } - } - `, - "non-openconfig-extensions": ` - module non-openconfig-extensions { - prefix "n"; - namespace "urn:n"; - - extension bar { - argument "baz"; - } - - extension posix-pattern { - argument "pattern"; - } - } - `, - } - - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - for n, m := range inModules { - if err := ms.Parse(m, n); err != nil { - t.Fatalf("error parsing module %s, got: %v, want: nil", n, err) - } - } - errs := ms.Process() - var err error - if len(errs) > 1 { - t.Fatalf("Got more than 1 error: %v", errs) - } else if len(errs) == 1 { - err = errs[0] - } - if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Errorf("Did not get expected error: %s", diff) - } - if err != nil { - return - } - - yangType, err := getTestLeaf(ms) - if err != nil { - t.Fatal(err) - } - - gotType := &YangType{} - populatePatterns(yangType, gotType) - if diff := cmp.Diff(gotType, tt.wantType, cmpopts.EquateEmpty()); diff != "" { - t.Errorf("Type.resolve() pattern test (-got, +want):\n%s", diff) - } - }) - } -} - -// populatePatterns populates targetType with only the -// Pattern/POSIXPattern fields of the given type, preserving -// the recursive structure of the type, to work around cmp not -// having an allowlist way of specifying which fields to -// compare. -func populatePatterns(ytype *YangType, targetType *YangType) { - targetType.Pattern = ytype.Pattern - targetType.POSIXPattern = ytype.POSIXPattern - for _, subtype := range ytype.Type { - targetSubtype := &YangType{} - targetType.Type = append(targetType.Type, targetSubtype) - populatePatterns(subtype, targetSubtype) - } -} - -func TestTypeLengthRange(t *testing.T) { - tests := []struct { - desc string - leafNode string - wantType *testRangeTypeStruct - wantErrSubstr string - }{{ - desc: "simple uint32", - leafNode: ` - typedef alpha { - type uint32 { - range "1..4 | 10..20"; - } - } - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "alpha", - Range: YangRange{R(1, 4), R(10, 20)}, - }, - }, { - desc: "inherited uint32", - leafNode: ` - typedef alpha { - type uint32 { - range "1..4 | 10..20"; - } - } - typedef bravo { - type alpha { - range "min..3 | 12..max"; - } - } - leaf test-leaf { - type bravo; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "bravo", - Range: YangRange{R(1, 3), R(12, 20)}, - }, - }, { - desc: "inherited uint32 range violation", - leafNode: ` - typedef alpha { - type uint32 { - range "1..4 | 10..20"; - } - } - typedef bravo { - type alpha { - range "min..max"; - } - } - leaf test-leaf { - type bravo; - } - } // end module`, - wantErrSubstr: "not within", - }, { - desc: "unrestricted decimal64", - leafNode: ` - typedef alpha { - type decimal64 { - fraction-digits 2; - } - } - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "alpha", - Range: YangRange{Rf(MinInt64, MaxInt64, 2)}, - }, - }, { - desc: "simple restricted decimal64", - leafNode: ` - typedef alpha { - type decimal64 { - fraction-digits 2; - range "1 .. 3.14 | 10 | 20..max"; - } - } - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "alpha", - Range: YangRange{Rf(100, 314, 2), Rf(1000, 1000, 2), Rf(2000, MaxInt64, 2)}, - }, - }, { - desc: "simple decimal64 with inherited ranges", - leafNode: ` - typedef alpha { - type decimal64 { - fraction-digits 3; - range "1 .. 3.14 | 10 | 20..max"; - } - } - typedef bravo { - type alpha { - range "min .. 2.72 | 42 .. max"; - } - } - leaf test-leaf { - type bravo; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "bravo", - Range: YangRange{Rf(1000, 2720, 3), Rf(42000, MaxInt64, 3)}, - }, - }, { - desc: "triple-inherited decimal64", - leafNode: ` - typedef alpha { - type decimal64 { - fraction-digits 2; - } - } - typedef bravo { - type alpha { - range "1 .. 3.14 | 10 | 20..max"; - } - } - typedef charlie { - type bravo { - range "min .. 2.72 | 42 .. max"; - } - } - leaf test-leaf { - type charlie; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "charlie", - Range: YangRange{Rf(100, 272, 2), Rf(4200, MaxInt64, 2)}, - }, - }, { - desc: "simple decimal64 with inherited ranges", - leafNode: ` - typedef alpha { - type decimal64 { - fraction-digits 2; - range "1 .. 3.14 | 10 | 20..max"; - } - } - typedef bravo { - type alpha { - range "min..max"; - } - } - leaf test-leaf { - type alpha; - } - } // end module`, - wantErrSubstr: "not within", - }, { - desc: "simple decimal64 with too few fractional digits", - leafNode: ` - typedef alpha { - type decimal64 { - fraction-digits 1; - range "1 .. 3.14 | 10 | 20..max"; - } - } - leaf test-leaf { - type alpha; - } - } // end module`, - wantErrSubstr: "has too much precision", - }, { - desc: "simple decimal64 fractional digit on inherited decimal64 type", - leafNode: ` - typedef alpha { - type decimal64 { - fraction-digits 2; - range "1 .. 3.14 | 10 | 20..max"; - } - } - typedef bravo { - type alpha { - fraction-digits 2; - range "25..max"; - } - } - leaf test-leaf { - type bravo; - } - } // end module`, - wantErrSubstr: "overriding of fraction-digits not allowed", - }, { - desc: "simple string with length", - leafNode: ` - typedef alpha { - type string { - length "1..4 | 10..20 | 30..max"; - } - } - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "alpha", - Length: YangRange{R(1, 4), R(10, 20), YRange{FromInt(30), FromUint(maxUint64)}}, - }, - }, { - desc: "inherited string", - leafNode: ` - typedef alpha { - type string { - length "1..4 | 10..20 | 30..max"; - } - } - typedef bravo { - type alpha { - length "min..3 | 42..max"; - } - } - leaf test-leaf { - type bravo; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "bravo", - Length: YangRange{R(1, 3), YRange{FromInt(42), FromUint(maxUint64)}}, - }, - }, { - desc: "inherited binary", - leafNode: ` - typedef alpha { - type binary { - length "1..4 | 10..20 | 30..max"; - } - } - typedef bravo { - type alpha { - length "min..3 | 42..max"; - } - } - leaf test-leaf { - type bravo; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "bravo", - Length: YangRange{R(1, 3), YRange{FromInt(42), FromUint(maxUint64)}}, - }, - }, { - desc: "inherited string length violation", - leafNode: ` - typedef alpha { - type string { - length "1..4 | 10..20 | 30..max"; - } - } - typedef bravo { - type alpha { - length "min..max"; - } - } - leaf test-leaf { - type bravo; - } - } // end module`, - wantErrSubstr: "not within", - }, { - desc: "simple union", - leafNode: ` - typedef alpha { - type union { - type string; - type binary { - length "min..5|999..max"; - } - type int8 { - range "min..-42|42..max"; - } - type enumeration { - enum zero; - enum one; - enum seven { - value 7; - } - } - } - } - leaf test-leaf { - type alpha; - } - } // end module`, - wantType: &testRangeTypeStruct{ - Name: "alpha", - Type: []*testRangeTypeStruct{{ - Name: "string", - }, { - Name: "binary", - Length: YangRange{R(0, 5), YRange{FromInt(999), FromUint(maxUint64)}}, - }, { - Name: "int8", - Range: YangRange{R(minInt8, -42), R(42, maxInt8)}, - }, { - Name: "enumeration", - }}, - }, - }} - - getTestLeaf := func(ms *Modules) (*YangType, error) { - const moduleName = "test" - m, ok := ms.Modules[moduleName] - if !ok { - return nil, fmt.Errorf("module not found: %q", moduleName) - } - if len(m.Leaf) == 0 { - return nil, fmt.Errorf("node %v is missing imports", m) - } - e := ToEntry(m) - return e.Dir["test-leaf"].Type, nil - } - - for _, tt := range tests { - inModules := map[string]string{ - "test": ` - module test { - prefix "t"; - namespace "urn:t"; - ` + tt.leafNode, - } - - t.Run(tt.desc, func(t *testing.T) { - ms := NewModules() - for n, m := range inModules { - if err := ms.Parse(m, n); err != nil { - t.Fatalf("error parsing module %s, got: %v, want: nil", n, err) - } - } - errs := ms.Process() - var err error - if len(errs) > 1 { - t.Fatalf("Got more than 1 error: %v", errs) - } else if len(errs) == 1 { - err = errs[0] - } - if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { - t.Errorf("Did not get expected error: %s", diff) - } - if err != nil { - return - } - - gotType, err := getTestLeaf(ms) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(filterRanges(gotType), tt.wantType); diff != "" { - t.Errorf("Type.resolve() union types test (-got, +want):\n%s", diff) - } - }) - } -} - -// testRangeTypeStruct is a filtered-down version of YangType where only certain -// fields are preserved for targeted testing. -type testRangeTypeStruct struct { - Name string - Length YangRange - Range YangRange - Type []*testRangeTypeStruct -} - -// filterRanges returns a testRangeTypeStruct with only the Name, Length, and Range -// fields of the given YangType, preserving the recursive structure of the -// type, to work around cmp not having an allowlist way of specifying which -// fields to compare and YangType having a custom Equal function. -func filterRanges(ytype *YangType) *testRangeTypeStruct { - filteredType := &testRangeTypeStruct{Name: ytype.Name} - filteredType.Length = ytype.Length - filteredType.Range = ytype.Range - for _, subtype := range ytype.Type { - filteredType.Type = append(filteredType.Type, filterRanges(subtype)) - } - return filteredType -} diff --git a/src/webui/internal/goyang/pkg/yang/yang.go b/src/webui/internal/goyang/pkg/yang/yang.go deleted file mode 100644 index fad667e25..000000000 --- a/src/webui/internal/goyang/pkg/yang/yang.go +++ /dev/null @@ -1,1101 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import "fmt" - -// This file contains the definitions for all nodes of the yang AST. -// The actual building of the AST is in ast.go - -// Some field names have specific meanings: -// -// Grouping - This field must always be of type []*Grouping -// Typedef - This field must always be of type []*Typedef - -// A Value is just a string that can have extensions. -type Value struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:",omitempty"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - Description *Value `yang:"description" json:",omitempty"` - Reference *Value `yang:"reference" json:",omitempty"` -} - -func (Value) Kind() string { return "string" } -func (s *Value) ParentNode() Node { return s.Parent } -func (s *Value) NName() string { return s.Name } -func (s *Value) Statement() *Statement { return s.Source } -func (s *Value) Exts() []*Statement { return s.Extensions } - -// asRangeInt returns the value v as an int64 if it is between the values of -// min and max inclusive. An error is returned if v is out of range or does -// not parse into a number. If v is nil then an error is returned. -func (s *Value) asRangeInt(min, max int64) (int64, error) { - if s == nil { - return 0, fmt.Errorf("value is required in the range of [%d..%d]", min, max) - } - n, err := ParseInt(s.Name) - if err != nil { - return 0, err - } - i, err := n.Int() - if err != nil { - return 0, err - } - if i < min || i > max { - return 0, fmt.Errorf("value %s out of range [%d..%d]", s.Name, min, max) - } - return i, nil -} - -// asBool returns v as a boolean (true or flase) or returns an error if v -// is neither true nor false. If v is nil then false is returned. -func (s *Value) asBool() (bool, error) { - // A missing value is considered false - if s == nil { - return false, nil - } - switch s.Name { - case "true": - return true, nil - case "false": - return false, nil - default: - return false, fmt.Errorf("invalid boolean: %s", s.Name) - } -} - -// asString simply returns the string value of v. If v is nil then an empty -// string is returned. -func (s *Value) asString() string { - if s == nil { - return "" - } - return s.Name -} - -// See http://tools.ietf.org/html/rfc6020#section-7 for a description of the -// following structures. The structures are derived from that document. - -// A Module is defined in: http://tools.ietf.org/html/rfc6020#section-7.1 -// -// A SubModule is defined in: http://tools.ietf.org/html/rfc6020#section-7.2 -type Module struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Anyxml []*AnyXML `yang:"anyxml"` - Augment []*Augment `yang:"augment"` - BelongsTo *BelongsTo `yang:"belongs-to,required=submodule,nomerge"` - Choice []*Choice `yang:"choice"` - Contact *Value `yang:"contact,nomerge"` - Container []*Container `yang:"container"` - Description *Value `yang:"description,nomerge"` - Deviation []*Deviation `yang:"deviation"` - Extension []*Extension `yang:"extension"` - Feature []*Feature `yang:"feature"` - Grouping []*Grouping `yang:"grouping"` - Identity []*Identity `yang:"identity"` - Import []*Import `yang:"import"` - Include []*Include `yang:"include"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Namespace *Value `yang:"namespace,required=module,nomerge"` - Notification []*Notification `yang:"notification"` - Organization *Value `yang:"organization,nomerge"` - Prefix *Value `yang:"prefix,required=module,nomerge"` - Reference *Value `yang:"reference,nomerge"` - Revision []*Revision `yang:"revision,nomerge"` - RPC []*RPC `yang:"rpc"` - Typedef []*Typedef `yang:"typedef"` - Uses []*Uses `yang:"uses"` - YangVersion *Value `yang:"yang-version,nomerge"` - - // Modules references the Modules object from which this Module node - // was parsed. - Modules *Modules -} - -func (s *Module) Kind() string { - if s.BelongsTo != nil { - return "submodule" - } - return "module" -} -func (s *Module) ParentNode() Node { return s.Parent } -func (s *Module) NName() string { return s.Name } -func (s *Module) Statement() *Statement { return s.Source } -func (s *Module) Exts() []*Statement { return s.Extensions } -func (s *Module) Groupings() []*Grouping { return s.Grouping } -func (s *Module) Typedefs() []*Typedef { return s.Typedef } -func (s *Module) Identities() []*Identity { return s.Identity } - -// Current returns the most recent revision of this module, or "" if the module -// has no revisions. -func (s *Module) Current() string { - var rev string - for _, r := range s.Revision { - if r.Name > rev { - rev = r.Name - } - } - return rev -} - -// FullName returns the full name of the module including the most recent -// revision, if any. -func (s *Module) FullName() string { - if rev := s.Current(); rev != "" { - return s.Name + "@" + rev - } - return s.Name -} - -// GetPrefix returns the proper prefix of m. Useful when looking up types -// in modules found by FindModuleByPrefix. -func (s *Module) GetPrefix() string { - pfx := s.getPrefix() - if pfx == nil { - // This case can be true during testing. - return "" - } - return pfx.Name -} - -// getPrefix returns the local prefix of the module used to refer to itself. -func (s *Module) getPrefix() *Value { - switch { - case s == nil: - return nil - case s.Kind() == "module" && s.Prefix != nil: - return s.Prefix - case s.Kind() == "submodule" && s.BelongsTo != nil: - return s.BelongsTo.Prefix - default: - return nil - } -} - -// An Import is defined in: http://tools.ietf.org/html/rfc6020#section-7.1.5 -type Import struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext"` - - Prefix *Value `yang:"prefix,required"` - RevisionDate *Value `yang:"revision-date"` - Reference *Value `yang:"reference,nomerge"` - Description *Value `yang:"description,nomerge"` - - // Module is the imported module. The types and groupings are - // available to the importer with the defined prefix. - Module *Module -} - -func (Import) Kind() string { return "import" } -func (s *Import) ParentNode() Node { return s.Parent } -func (s *Import) NName() string { return s.Name } -func (s *Import) Statement() *Statement { return s.Source } -func (s *Import) Exts() []*Statement { return s.Extensions } - -// An Include is defined in: http://tools.ietf.org/html/rfc6020#section-7.1.6 -type Include struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - RevisionDate *Value `yang:"revision-date"` - - // Module is the included module. The types and groupings are - // available to the importer with the defined prefix. - Module *Module -} - -func (Include) Kind() string { return "include" } -func (s *Include) ParentNode() Node { return s.Parent } -func (s *Include) NName() string { return s.Name } -func (s *Include) Statement() *Statement { return s.Source } -func (s *Include) Exts() []*Statement { return s.Extensions } - -// A Revision is defined in: http://tools.ietf.org/html/rfc6020#section-7.1.9 -type Revision struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - Description *Value `yang:"description"` - Reference *Value `yang:"reference"` -} - -func (Revision) Kind() string { return "revision" } -func (s *Revision) ParentNode() Node { return s.Parent } -func (s *Revision) NName() string { return s.Name } -func (s *Revision) Statement() *Statement { return s.Source } -func (s *Revision) Exts() []*Statement { return s.Extensions } - -// A BelongsTo is defined in: http://tools.ietf.org/html/rfc6020#section-7.2.2 -type BelongsTo struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - Prefix *Value `yang:"prefix,required"` -} - -func (BelongsTo) Kind() string { return "belongs-to" } -func (s *BelongsTo) ParentNode() Node { return s.Parent } -func (s *BelongsTo) NName() string { return s.Name } -func (s *BelongsTo) Statement() *Statement { return s.Source } -func (s *BelongsTo) Exts() []*Statement { return s.Extensions } - -// A Typedef is defined in: http://tools.ietf.org/html/rfc6020#section-7.3 -type Typedef struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Default *Value `yang:"default"` - Description *Value `yang:"description"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Type *Type `yang:"type,required"` - Units *Value `yang:"units"` - - YangType *YangType `json:"-"` -} - -func (Typedef) Kind() string { return "typedef" } -func (s *Typedef) ParentNode() Node { return s.Parent } -func (s *Typedef) NName() string { return s.Name } -func (s *Typedef) Statement() *Statement { return s.Source } -func (s *Typedef) Exts() []*Statement { return s.Extensions } - -// A Type is defined in: http://tools.ietf.org/html/rfc6020#section-7.4 -// Note that Name is the name of the type we want, it is what must -// be looked up and resolved. -type Type struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - IdentityBase *Value `yang:"base"` // Name == identityref - Bit []*Bit `yang:"bit"` - Enum []*Enum `yang:"enum"` - FractionDigits *Value `yang:"fraction-digits"` // Name == decimal64 - Length *Length `yang:"length"` - Path *Value `yang:"path"` - Pattern []*Pattern `yang:"pattern"` - Range *Range `yang:"range"` - RequireInstance *Value `yang:"require-instance"` - Type []*Type `yang:"type"` // len > 1 only when Name is "union" - - YangType *YangType -} - -func (Type) Kind() string { return "type" } -func (s *Type) ParentNode() Node { return s.Parent } -func (s *Type) NName() string { return s.Name } -func (s *Type) Statement() *Statement { return s.Source } -func (s *Type) Exts() []*Statement { return s.Extensions } - -// A Container is defined in: http://tools.ietf.org/html/rfc6020#section-7.5 -// and http://tools.ietf.org/html/rfc7950#section-7.5 ("container" sub-statement) -type Container struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Action []*Action `yang:"action"` - Anyxml []*AnyXML `yang:"anyxml"` - Choice []*Choice `yang:"choice"` - Config *Value `yang:"config"` - Container []*Container `yang:"container"` - Description *Value `yang:"description"` - Grouping []*Grouping `yang:"grouping"` - IfFeature []*Value `yang:"if-feature"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Must []*Must `yang:"must"` - Notification []*Notification `yang:"notification"` - Presence *Value `yang:"presence"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Typedef []*Typedef `yang:"typedef"` - Uses []*Uses `yang:"uses"` - When *Value `yang:"when"` -} - -func (Container) Kind() string { return "container" } -func (s *Container) ParentNode() Node { return s.Parent } -func (s *Container) NName() string { return s.Name } -func (s *Container) Statement() *Statement { return s.Source } -func (s *Container) Exts() []*Statement { return s.Extensions } -func (s *Container) Groupings() []*Grouping { return s.Grouping } -func (s *Container) Typedefs() []*Typedef { return s.Typedef } - -// A Must is defined in: http://tools.ietf.org/html/rfc6020#section-7.5.3 -type Must struct { - Name string `yang:"Name,nomerge" json:",omitempty"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - Description *Value `yang:"description" json:",omitempty"` - ErrorAppTag *Value `yang:"error-app-tag" json:",omitempty"` - ErrorMessage *Value `yang:"error-message" json:",omitempty"` - Reference *Value `yang:"reference" json:",omitempty"` -} - -func (Must) Kind() string { return "must" } -func (s *Must) ParentNode() Node { return s.Parent } -func (s *Must) NName() string { return s.Name } -func (s *Must) Statement() *Statement { return s.Source } -func (s *Must) Exts() []*Statement { return s.Extensions } - -// A Leaf is defined in: http://tools.ietf.org/html/rfc6020#section-7.6 -type Leaf struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Config *Value `yang:"config"` - Default *Value `yang:"default"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Mandatory *Value `yang:"mandatory"` - Must []*Must `yang:"must"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Type *Type `yang:"type,required"` - Units *Value `yang:"units"` - When *Value `yang:"when"` -} - -func (Leaf) Kind() string { return "leaf" } -func (s *Leaf) ParentNode() Node { return s.Parent } -func (s *Leaf) NName() string { return s.Name } -func (s *Leaf) Statement() *Statement { return s.Source } -func (s *Leaf) Exts() []*Statement { return s.Extensions } - -// A LeafList is defined in: -// YANG 1: http://tools.ietf.org/html/rfc6020#section-7.7 -// YANG 1.1: https://tools.ietf.org/html/rfc7950#section-7.7 -// It this is supposed to be an array of nodes.. -type LeafList struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Config *Value `yang:"config"` - Default []*Value `yang:"default"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - MaxElements *Value `yang:"max-elements"` - MinElements *Value `yang:"min-elements"` - Must []*Must `yang:"must"` - OrderedBy *Value `yang:"ordered-by"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Type *Type `yang:"type,required"` - Units *Value `yang:"units"` - When *Value `yang:"when"` -} - -func (LeafList) Kind() string { return "leaf-list" } -func (s *LeafList) ParentNode() Node { return s.Parent } -func (s *LeafList) NName() string { return s.Name } -func (s *LeafList) Statement() *Statement { return s.Source } -func (s *LeafList) Exts() []*Statement { return s.Extensions } - -// A List is defined in: http://tools.ietf.org/html/rfc6020#section-7.8 -// and http://tools.ietf.org/html/rfc7950#section-7.8 ("list" sub-statement) -type List struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Action []*Action `yang:"action"` - Anyxml []*AnyXML `yang:"anyxml"` - Choice []*Choice `yang:"choice"` - Config *Value `yang:"config"` - Container []*Container `yang:"container"` - Description *Value `yang:"description"` - Grouping []*Grouping `yang:"grouping"` - IfFeature []*Value `yang:"if-feature"` - Key *Value `yang:"key"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - MaxElements *Value `yang:"max-elements"` - MinElements *Value `yang:"min-elements"` - Must []*Must `yang:"must"` - Notification []*Notification `yang:"notification"` - OrderedBy *Value `yang:"ordered-by"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Typedef []*Typedef `yang:"typedef"` - Unique []*Value `yang:"unique"` - Uses []*Uses `yang:"uses"` - When *Value `yang:"when"` -} - -func (List) Kind() string { return "list" } -func (s *List) ParentNode() Node { return s.Parent } -func (s *List) NName() string { return s.Name } -func (s *List) Statement() *Statement { return s.Source } -func (s *List) Exts() []*Statement { return s.Extensions } -func (s *List) Groupings() []*Grouping { return s.Grouping } -func (s *List) Typedefs() []*Typedef { return s.Typedef } - -// A Choice is defined in: http://tools.ietf.org/html/rfc6020#section-7.9 -type Choice struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Anyxml []*AnyXML `yang:"anyxml"` - Case []*Case `yang:"case"` - Config *Value `yang:"config"` - Container []*Container `yang:"container"` - Default *Value `yang:"default"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Mandatory *Value `yang:"mandatory"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - When *Value `yang:"when"` -} - -func (Choice) Kind() string { return "choice" } -func (s *Choice) ParentNode() Node { return s.Parent } -func (s *Choice) NName() string { return s.Name } -func (s *Choice) Statement() *Statement { return s.Source } -func (s *Choice) Exts() []*Statement { return s.Extensions } - -// A Case is defined in: http://tools.ietf.org/html/rfc6020#section-7.9.2 -type Case struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Anyxml []*AnyXML `yang:"anyxml"` - Choice []*Choice `yang:"choice"` - Container []*Container `yang:"container"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Uses []*Uses `yang:"uses"` - When *Value `yang:"when"` -} - -func (Case) Kind() string { return "case" } -func (s *Case) ParentNode() Node { return s.Parent } -func (s *Case) NName() string { return s.Name } -func (s *Case) Statement() *Statement { return s.Source } -func (s *Case) Exts() []*Statement { return s.Extensions } - -// An AnyXML is defined in: http://tools.ietf.org/html/rfc6020#section-7.10 -type AnyXML struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Config *Value `yang:"config"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Mandatory *Value `yang:"mandatory"` - Must []*Must `yang:"must"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - When *Value `yang:"when"` -} - -func (AnyXML) Kind() string { return "anyxml" } -func (s *AnyXML) ParentNode() Node { return s.Parent } -func (s *AnyXML) NName() string { return s.Name } -func (s *AnyXML) Statement() *Statement { return s.Source } -func (s *AnyXML) Exts() []*Statement { return s.Extensions } - -// An AnyData is defined in: http://tools.ietf.org/html/rfc7950#section-7.10 -// -// AnyData are only expected in YANG 1.1 modules (those with a -// "yang-version 1.1;" statement in the module). -type AnyData struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Config *Value `yang:"config"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Mandatory *Value `yang:"mandatory"` - Must []*Must `yang:"must"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - When *Value `yang:"when"` -} - -func (AnyData) Kind() string { return "anydata" } -func (s *AnyData) ParentNode() Node { return s.Parent } -func (s *AnyData) NName() string { return s.Name } -func (s *AnyData) Statement() *Statement { return s.Source } -func (s *AnyData) Exts() []*Statement { return s.Extensions } - -// A Grouping is defined in: http://tools.ietf.org/html/rfc6020#section-7.11 -// and http://tools.ietf.org/html/rfc7950#section-7.12 ("grouping" sub-statement) -type Grouping struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Action []*Action `yang:"action"` - Anyxml []*AnyXML `yang:"anyxml"` - Choice []*Choice `yang:"choice"` - Container []*Container `yang:"container"` - Description *Value `yang:"description"` - Grouping []*Grouping `yang:"grouping"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Notification []*Notification `yang:"notification"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Typedef []*Typedef `yang:"typedef"` - Uses []*Uses `yang:"uses"` -} - -func (Grouping) Kind() string { return "grouping" } -func (s *Grouping) ParentNode() Node { return s.Parent } -func (s *Grouping) NName() string { return s.Name } -func (s *Grouping) Statement() *Statement { return s.Source } -func (s *Grouping) Exts() []*Statement { return s.Extensions } -func (s *Grouping) Groupings() []*Grouping { return s.Grouping } -func (s *Grouping) Typedefs() []*Typedef { return s.Typedef } - -// A Uses is defined in: http://tools.ietf.org/html/rfc6020#section-7.12 -type Uses struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:"-"` - - Augment []*Augment `yang:"augment" json:",omitempty"` - Description *Value `yang:"description" json:",omitempty"` - IfFeature []*Value `yang:"if-feature" json:"-"` - Refine []*Refine `yang:"refine" json:"-"` - Reference *Value `yang:"reference" json:"-"` - Status *Value `yang:"status" json:"-"` - When *Value `yang:"when" json:",omitempty"` -} - -func (Uses) Kind() string { return "uses" } -func (s *Uses) ParentNode() Node { return s.Parent } -func (s *Uses) NName() string { return s.Name } -func (s *Uses) Statement() *Statement { return s.Source } -func (s *Uses) Exts() []*Statement { return s.Extensions } - -// A Refine is defined in: http://tools.ietf.org/html/rfc6020#section-7.12.2 -type Refine struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Default *Value `yang:"default"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Reference *Value `yang:"reference"` - Config *Value `yang:"config"` - Mandatory *Value `yang:"mandatory"` - Presence *Value `yang:"presence"` - Must []*Must `yang:"must"` - MaxElements *Value `yang:"max-elements"` - MinElements *Value `yang:"min-elements"` -} - -func (Refine) Kind() string { return "refine" } -func (s *Refine) ParentNode() Node { return s.Parent } -func (s *Refine) NName() string { return s.Name } -func (s *Refine) Statement() *Statement { return s.Source } -func (s *Refine) Exts() []*Statement { return s.Extensions } - -// An RPC is defined in: http://tools.ietf.org/html/rfc6020#section-7.13 -type RPC struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - Grouping []*Grouping `yang:"grouping"` - IfFeature []*Value `yang:"if-feature"` - Input *Input `yang:"input"` - Output *Output `yang:"output"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Typedef []*Typedef `yang:"typedef"` -} - -func (RPC) Kind() string { return "rpc" } -func (s *RPC) ParentNode() Node { return s.Parent } -func (s *RPC) NName() string { return s.Name } -func (s *RPC) Statement() *Statement { return s.Source } -func (s *RPC) Exts() []*Statement { return s.Extensions } -func (s *RPC) Groupings() []*Grouping { return s.Grouping } -func (s *RPC) Typedefs() []*Typedef { return s.Typedef } - -// An Input is defined in: http://tools.ietf.org/html/rfc6020#section-7.13.2 -type Input struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Anyxml []*AnyXML `yang:"anyxml"` - Choice []*Choice `yang:"choice"` - Container []*Container `yang:"container"` - Grouping []*Grouping `yang:"grouping"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Must []*Must `yang:"must"` - Typedef []*Typedef `yang:"typedef"` - Uses []*Uses `yang:"uses"` -} - -func (Input) Kind() string { return "input" } -func (s *Input) ParentNode() Node { return s.Parent } -func (s *Input) NName() string { return s.Name } -func (s *Input) Statement() *Statement { return s.Source } -func (s *Input) Exts() []*Statement { return s.Extensions } -func (s *Input) Groupings() []*Grouping { return s.Grouping } -func (s *Input) Typedefs() []*Typedef { return s.Typedef } - -// An Output is defined in: http://tools.ietf.org/html/rfc6020#section-7.13.3 -type Output struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Anyxml []*AnyXML `yang:"anyxml"` - Choice []*Choice `yang:"choice"` - Container []*Container `yang:"container"` - Grouping []*Grouping `yang:"grouping"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Must []*Must `yang:"must"` - Typedef []*Typedef `yang:"typedef"` - Uses []*Uses `yang:"uses"` -} - -func (Output) Kind() string { return "output" } -func (s *Output) ParentNode() Node { return s.Parent } -func (s *Output) NName() string { return s.Name } -func (s *Output) Statement() *Statement { return s.Source } -func (s *Output) Exts() []*Statement { return s.Extensions } -func (s *Output) Groupings() []*Grouping { return s.Grouping } -func (s *Output) Typedefs() []*Typedef { return s.Typedef } - -// A Notification is defined in: http://tools.ietf.org/html/rfc6020#section-7.14 -type Notification struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Anyxml []*AnyXML `yang:"anyxml"` - Choice []*Choice `yang:"choice"` - Container []*Container `yang:"container"` - Description *Value `yang:"description"` - Grouping []*Grouping `yang:"grouping"` - IfFeature []*Value `yang:"if-feature"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Typedef []*Typedef `yang:"typedef"` - Uses []*Uses `yang:"uses"` -} - -func (Notification) Kind() string { return "notification" } -func (s *Notification) ParentNode() Node { return s.Parent } -func (s *Notification) NName() string { return s.Name } -func (s *Notification) Statement() *Statement { return s.Source } -func (s *Notification) Exts() []*Statement { return s.Extensions } -func (s *Notification) Groupings() []*Grouping { return s.Grouping } -func (s *Notification) Typedefs() []*Typedef { return s.Typedef } - -// An Augment is defined in: http://tools.ietf.org/html/rfc6020#section-7.15 -// and http://tools.ietf.org/html/rfc7950#section-7.17 ("augment" sub-statement) -type Augment struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Anydata []*AnyData `yang:"anydata"` - Action []*Action `yang:"action"` - Anyxml []*AnyXML `yang:"anyxml"` - Case []*Case `yang:"case"` - Choice []*Choice `yang:"choice"` - Container []*Container `yang:"container"` - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Leaf []*Leaf `yang:"leaf"` - LeafList []*LeafList `yang:"leaf-list"` - List []*List `yang:"list"` - Notification []*Notification `yang:"notification"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Uses []*Uses `yang:"uses"` - When *Value `yang:"when"` -} - -func (Augment) Kind() string { return "augment" } -func (s *Augment) ParentNode() Node { return s.Parent } -func (s *Augment) NName() string { return s.Name } -func (s *Augment) Statement() *Statement { return s.Source } -func (s *Augment) Exts() []*Statement { return s.Extensions } - -// An Identity is defined in: http://tools.ietf.org/html/rfc6020#section-7.16 -type Identity struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:"-"` - - Base []*Value `yang:"base" json:"-"` - Description *Value `yang:"description" json:"-"` - IfFeature []*Value `yang:"if-feature" json:"-"` - Reference *Value `yang:"reference" json:"-"` - Status *Value `yang:"status" json:"-"` - Values []*Identity `json:",omitempty"` -} - -func (Identity) Kind() string { return "identity" } -func (s *Identity) ParentNode() Node { return s.Parent } -func (s *Identity) NName() string { return s.Name } -func (s *Identity) Statement() *Statement { return s.Source } -func (s *Identity) Exts() []*Statement { return s.Extensions } - -// PrefixedName returns the prefix-qualified name for the identity -func (s *Identity) PrefixedName() string { - return fmt.Sprintf("%s:%s", RootNode(s).GetPrefix(), s.Name) -} - -// modulePrefixedName returns the module-qualified name for the identity. -func (s *Identity) modulePrefixedName() string { - return fmt.Sprintf("%s:%s", module(s).Name, s.Name) -} - -// IsDefined behaves the same as the implementation for Enum - it returns -// true if an identity with the name is defined within the Values of the -// identity -func (s *Identity) IsDefined(name string) bool { - return s.GetValue(name) != nil -} - -// GetValue returns a pointer to the identity with name "name" that is within -// the values of the identity -func (s *Identity) GetValue(name string) *Identity { - for _, v := range s.Values { - if v.Name == name { - return v - } - } - return nil -} - -// An Extension is defined in: http://tools.ietf.org/html/rfc6020#section-7.17 -type Extension struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - Argument *Argument `yang:"argument" json:",omitempty"` - Description *Value `yang:"description" json:",omitempty"` - Reference *Value `yang:"reference" json:",omitempty"` - Status *Value `yang:"status" json:",omitempty"` -} - -func (Extension) Kind() string { return "extension" } -func (s *Extension) ParentNode() Node { return s.Parent } -func (s *Extension) NName() string { return s.Name } -func (s *Extension) Statement() *Statement { return s.Source } -func (s *Extension) Exts() []*Statement { return s.Extensions } - -// An Argument is defined in: http://tools.ietf.org/html/rfc6020#section-7.17.2 -type Argument struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - YinElement *Value `yang:"yin-element" json:",omitempty"` -} - -func (Argument) Kind() string { return "argument" } -func (s *Argument) ParentNode() Node { return s.Parent } -func (s *Argument) NName() string { return s.Name } -func (s *Argument) Statement() *Statement { return s.Source } -func (s *Argument) Exts() []*Statement { return s.Extensions } - -// An Element is defined in: http://tools.ietf.org/html/rfc6020#section-7.17.2.2 -type Element struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - YinElement *Value `yang:"yin-element"` -} - -func (Element) Kind() string { return "element" } -func (s *Element) ParentNode() Node { return s.Parent } -func (s *Element) NName() string { return s.Name } -func (s *Element) Statement() *Statement { return s.Source } -func (s *Element) Exts() []*Statement { return s.Extensions } - -// A Feature is defined in: http://tools.ietf.org/html/rfc6020#section-7.18.1 -type Feature struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge" json:"-"` - Parent Node `yang:"Parent,nomerge" json:"-"` - Extensions []*Statement `yang:"Ext" json:",omitempty"` - - Description *Value `yang:"description" json:",omitempty"` - IfFeature []*Value `yang:"if-feature" json:",omitempty"` - Status *Value `yang:"status" json:",omitempty"` - Reference *Value `yang:"reference" json:",omitempty"` -} - -func (Feature) Kind() string { return "feature" } -func (s *Feature) ParentNode() Node { return s.Parent } -func (s *Feature) NName() string { return s.Name } -func (s *Feature) Statement() *Statement { return s.Source } -func (s *Feature) Exts() []*Statement { return s.Extensions } - -// A Deviation is defined in: http://tools.ietf.org/html/rfc6020#section-7.18.3 -type Deviation struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - Deviate []*Deviate `yang:"deviate,required"` - Reference *Value `yang:"reference"` -} - -func (Deviation) Kind() string { return "deviation" } -func (s *Deviation) ParentNode() Node { return s.Parent } -func (s *Deviation) NName() string { return s.Name } -func (s *Deviation) Statement() *Statement { return s.Source } -func (s *Deviation) Exts() []*Statement { return s.Extensions } - -// A Deviate is defined in: http://tools.ietf.org/html/rfc6020#section-7.18.3.2 -type Deviate struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Config *Value `yang:"config"` - Default *Value `yang:"default"` - Mandatory *Value `yang:"mandatory"` - MaxElements *Value `yang:"max-elements"` - MinElements *Value `yang:"min-elements"` - Must []*Must `yang:"must"` - Type *Type `yang:"type"` - Unique []*Value `yang:"unique"` - Units *Value `yang:"units"` -} - -func (Deviate) Kind() string { return "deviate" } -func (s *Deviate) ParentNode() Node { return s.Parent } -func (s *Deviate) NName() string { return s.Name } -func (s *Deviate) Statement() *Statement { return s.Source } -func (s *Deviate) Exts() []*Statement { return s.Extensions } - -// An Enum is defined in: http://tools.ietf.org/html/rfc6020#section-9.6.4 -type Enum struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Value *Value `yang:"value"` -} - -func (Enum) Kind() string { return "enum" } -func (s *Enum) ParentNode() Node { return s.Parent } -func (s *Enum) NName() string { return s.Name } -func (s *Enum) Statement() *Statement { return s.Source } -func (s *Enum) Exts() []*Statement { return s.Extensions } - -// A Bit is defined in: http://tools.ietf.org/html/rfc6020#section-9.7.4 -type Bit struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - IfFeature []*Value `yang:"if-feature"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Position *Value `yang:"position"` -} - -func (Bit) Kind() string { return "bit" } -func (s *Bit) ParentNode() Node { return s.Parent } -func (s *Bit) NName() string { return s.Name } -func (s *Bit) Statement() *Statement { return s.Source } -func (s *Bit) Exts() []*Statement { return s.Extensions } - -// A Range is defined in: http://tools.ietf.org/html/rfc6020#section-9.2.4 -type Range struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - ErrorAppTag *Value `yang:"error-app-tag"` - ErrorMessage *Value `yang:"error-message"` - Reference *Value `yang:"reference"` -} - -func (Range) Kind() string { return "range" } -func (s *Range) ParentNode() Node { return s.Parent } -func (s *Range) NName() string { return s.Name } -func (s *Range) Statement() *Statement { return s.Source } -func (s *Range) Exts() []*Statement { return s.Extensions } - -// A Length is defined in: http://tools.ietf.org/html/rfc6020#section-9.4.4 -type Length struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - ErrorAppTag *Value `yang:"error-app-tag"` - ErrorMessage *Value `yang:"error-message"` - Reference *Value `yang:"reference"` -} - -func (Length) Kind() string { return "length" } -func (s *Length) ParentNode() Node { return s.Parent } -func (s *Length) NName() string { return s.Name } -func (s *Length) Statement() *Statement { return s.Source } -func (s *Length) Exts() []*Statement { return s.Extensions } - -// A Pattern is defined in: http://tools.ietf.org/html/rfc6020#section-9.4.6 -// and http://tools.ietf.org/html/rfc7950#section-9.4.5.1 ("modifier" sub-statement) -type Pattern struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - ErrorAppTag *Value `yang:"error-app-tag"` - ErrorMessage *Value `yang:"error-message"` - Reference *Value `yang:"reference"` - Modifier *Value `yang:"modifier"` -} - -func (Pattern) Kind() string { return "pattern" } -func (s *Pattern) ParentNode() Node { return s.Parent } -func (s *Pattern) NName() string { return s.Name } -func (s *Pattern) Statement() *Statement { return s.Source } -func (s *Pattern) Exts() []*Statement { return s.Extensions } - -// An Action is defined in http://tools.ietf.org/html/rfc7950#section-7.15 -// -// Action define an RPC operation connected to a specific container or list data -// node in the schema. In the schema tree, Action differ from RPC only in where -// in the tree they are found. RPC nodes are only found as sub-statements of a -// Module, while Action are found only as sub-statements of Container, List, -// Grouping and Augment nodes. -type Action struct { - Name string `yang:"Name,nomerge"` - Source *Statement `yang:"Statement,nomerge"` - Parent Node `yang:"Parent,nomerge"` - Extensions []*Statement `yang:"Ext"` - - Description *Value `yang:"description"` - Grouping []*Grouping `yang:"grouping"` - IfFeature []*Value `yang:"if-feature"` - Input *Input `yang:"input"` - Output *Output `yang:"output"` - Reference *Value `yang:"reference"` - Status *Value `yang:"status"` - Typedef []*Typedef `yang:"typedef"` -} - -func (Action) Kind() string { return "action" } -func (s *Action) ParentNode() Node { return s.Parent } -func (s *Action) NName() string { return s.Name } -func (s *Action) Statement() *Statement { return s.Source } -func (s *Action) Exts() []*Statement { return s.Extensions } -func (s *Action) Groupings() []*Grouping { return s.Grouping } -func (s *Action) Typedefs() []*Typedef { return s.Typedef } diff --git a/src/webui/internal/goyang/pkg/yang/yangtype.go b/src/webui/internal/goyang/pkg/yang/yangtype.go deleted file mode 100644 index cc1e33fd9..000000000 --- a/src/webui/internal/goyang/pkg/yang/yangtype.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2021 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" -) - -var ( - // TypeKindFromName maps the string name used in a YANG file to the enumerated - // TypeKind used in this library. - TypeKindFromName = map[string]TypeKind{ - "none": Ynone, - "int8": Yint8, - "int16": Yint16, - "int32": Yint32, - "int64": Yint64, - "uint8": Yuint8, - "uint16": Yuint16, - "uint32": Yuint32, - "uint64": Yuint64, - "binary": Ybinary, - "bits": Ybits, - "boolean": Ybool, - "decimal64": Ydecimal64, - "empty": Yempty, - "enumeration": Yenum, - "identityref": Yidentityref, - "instance-identifier": YinstanceIdentifier, - "leafref": Yleafref, - "string": Ystring, - "union": Yunion, - } - - // TypeKindToName maps the enumerated type used in this library to the string - // used in a YANG file. - TypeKindToName = map[TypeKind]string{ - Ynone: "none", - Yint8: "int8", - Yint16: "int16", - Yint32: "int32", - Yint64: "int64", - Yuint8: "uint8", - Yuint16: "uint16", - Yuint32: "uint32", - Yuint64: "uint64", - Ybinary: "binary", - Ybits: "bits", - Ybool: "boolean", - Ydecimal64: "decimal64", - Yempty: "empty", - Yenum: "enumeration", - Yidentityref: "identityref", - YinstanceIdentifier: "instance-identifier", - Yleafref: "leafref", - Ystring: "string", - Yunion: "union", - } - - // BaseTypedefs is a map of all base types to the Typedef structure manufactured - // for the type. - BaseTypedefs = map[string]*Typedef{} - - baseTypes = map[string]*YangType{ - "int8": { - Name: "int8", - Kind: Yint8, - Range: Int8Range, - }, - "int16": { - Name: "int16", - Kind: Yint16, - Range: Int16Range, - }, - "int32": { - Name: "int32", - Kind: Yint32, - Range: Int32Range, - }, - "int64": { - Name: "int64", - Kind: Yint64, - Range: Int64Range, - }, - "uint8": { - Name: "uint8", - Kind: Yuint8, - Range: Uint8Range, - }, - "uint16": { - Name: "uint16", - Kind: Yuint16, - Range: Uint16Range, - }, - "uint32": { - Name: "uint32", - Kind: Yuint32, - Range: Uint32Range, - }, - "uint64": { - Name: "uint64", - Kind: Yuint64, - Range: Uint64Range, - }, - - "decimal64": { - Name: "decimal64", - Kind: Ydecimal64, - }, - "string": { - Name: "string", - Kind: Ystring, - }, - "boolean": { - Name: "boolean", - Kind: Ybool, - }, - "enumeration": { - Name: "enumeration", - Kind: Yenum, - }, - "bits": { - Name: "bits", - Kind: Ybits, - }, - "binary": { - Name: "binary", - Kind: Ybinary, - }, - "leafref": { - Name: "leafref", - Kind: Yleafref, - }, - "identityref": { - Name: "identityref", - Kind: Yidentityref, - }, - "empty": { - Name: "empty", - Kind: Yempty, - }, - "union": { - Name: "union", - Kind: Yunion, - }, - "instance-identifier": { - Name: "instance-identifier", - Kind: YinstanceIdentifier, - }, - } -) - -// Install builtin types as know types -func init() { - for k, v := range baseTypes { - // Base types are always their own root - v.Root = v - BaseTypedefs[k] = v.typedef() - } -} - -// TypeKind is the enumeration of the base types available in YANG. It -// is analogous to reflect.Kind. -type TypeKind uint - -func (k TypeKind) String() string { - if s := TypeKindToName[k]; s != "" { - return s - } - return fmt.Sprintf("unknown-type-%d", k) -} - -const ( - // Ynone represents the invalid (unset) type. - Ynone = TypeKind(iota) - // Yint8 is an int in the range [-128, 127]. - Yint8 - // Yint16 is an int in the range [-32768, 32767]. - Yint16 - // Yint32 is an int in the range [-2147483648, 2147483647]. - Yint32 - // Yint64 is an int in the range [-9223372036854775808, 9223372036854775807] - Yint64 - // Yuint8 is an int in the range [0, 255] - Yuint8 - // Yuint16 is an int in the range [0, 65535] - Yuint16 - // Yuint32 is an int in the range [0, 4294967295] - Yuint32 - // Yuint64 is an int in the range [0, 18446744073709551615] - Yuint64 - - // Ybinary stores arbitrary data. - Ybinary - // Ybits is a named set of bits or flags. - Ybits - // Ybool is true or false. - Ybool - // Ydecimal64 is a signed decimal number. - Ydecimal64 - // Yempty has no associated value. - Yempty - // Yenum stores enumerated strings. - Yenum - // Yidentityref stores an extensible enumeration. - Yidentityref - // YinstanceIdentifier stores a reference to a data tree node. - YinstanceIdentifier - // Yleafref stores a reference to a leaf instance. - Yleafref - // Ystring is a human readable string. - Ystring - // Yunion is a choice of types. - Yunion -) - -// A YangType is the internal representation of a type in YANG. It may -// refer to either a builtin type or type specified with typedef. Not -// all fields in YangType are used for all types. -type YangType struct { - Name string - Kind TypeKind // Ynone if not a base type - Base *Type `json:"-"` // Base type for non-builtin types - IdentityBase *Identity `json:",omitempty"` // Base statement for a type using identityref - Root *YangType `json:"-"` // root of this type that is the same - Bit *EnumType `json:",omitempty"` // bit position, "status" is lost - Enum *EnumType `json:",omitempty"` // enum name to value, "status" is lost - Units string `json:",omitempty"` // units to be used for this type - Default string `json:",omitempty"` // default value, if any - HasDefault bool `json:",omitempty"` // whether the type has a default. - FractionDigits int `json:",omitempty"` // decimal64 fixed point precision - Length YangRange `json:",omitempty"` // this should be processed by section 12 - OptionalInstance bool `json:",omitempty"` // !require-instances which defaults to true - Path string `json:",omitempty"` // the path in a leafref - Pattern []string `json:",omitempty"` // limiting XSD-TYPES expressions on strings - POSIXPattern []string `json:",omitempty"` // limiting POSIX ERE on strings (specified by openconfig-extensions:posix-pattern) - Range YangRange `json:",omitempty"` // range for integers - Type []*YangType `json:",omitempty"` // for unions -} - -// Equal returns true if y and t describe the same type. -func (y *YangType) Equal(t *YangType) bool { - switch { - case y == t: - return true - case y == nil || t == nil: - return false - case - // Don't check the Name, it contains no information - y.Kind != t.Kind, - y.Units != t.Units, - y.Default != t.Default, - y.HasDefault != t.HasDefault, - y.FractionDigits != t.FractionDigits, - y.IdentityBase != t.IdentityBase, - len(y.Length) != len(t.Length), - !y.Length.Equal(t.Length), - y.OptionalInstance != t.OptionalInstance, - y.Path != t.Path, - !ssEqual(y.Pattern, t.Pattern), - !ssEqual(y.POSIXPattern, t.POSIXPattern), - len(y.Range) != len(t.Range), - !y.Range.Equal(t.Range), - !tsEqual(y.Type, t.Type), - !cmp.Equal(y.Enum, t.Enum, cmp.Comparer(func(t, u EnumType) bool { - return cmp.Equal(t.unique, u.unique) && cmp.Equal(t.ToInt, u.ToInt) && cmp.Equal(t.ToString, u.ToString) - })): - - return false - } - // TODO(borman): Base, Bit - return true -} - -// typedef returns a Typedef created from y for insertion into the BaseTypedefs -// map. -func (y *YangType) typedef() *Typedef { - return &Typedef{ - Name: y.Name, - Source: &Statement{}, - Type: &Type{ - Name: y.Name, - Source: &Statement{}, - YangType: y, - }, - YangType: y, - } -} - -// ssEqual returns true if the two slices are equivalent. -func ssEqual(s1, s2 []string) bool { - if len(s1) != len(s2) { - return false - } - for x, s := range s1 { - if s != s2[x] { - return false - } - } - return true -} - -// tsEqual returns true if the two Type slices are identical. -func tsEqual(t1, t2 []*YangType) bool { - if len(t1) != len(t2) { - return false - } - // For now we compare absolute pointers. - // This may be wrong. - for x, t := range t1 { - if !t.Equal(t2[x]) { - return false - } - } - return true -} diff --git a/src/webui/internal/goyang/pkg/yang/yangtype_test.go b/src/webui/internal/goyang/pkg/yang/yangtype_test.go deleted file mode 100644 index f02e66e2c..000000000 --- a/src/webui/internal/goyang/pkg/yang/yangtype_test.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2021 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yang - -import ( - "testing" -) - -func TestYangTypeEqual(t *testing.T) { - - tests := []struct { - name string - inLeft *YangType - inRight *YangType - wantEqual bool - }{{ - name: "both-nil", - inLeft: nil, - inRight: nil, - wantEqual: true, - }, { - name: "one-nil", - inLeft: &YangType{ - Kind: Ydecimal64, - FractionDigits: 5, - }, - inRight: nil, - wantEqual: false, - }, { - name: "name-unequal", - inLeft: &YangType{ - Name: "foo", - Kind: Ydecimal64, - FractionDigits: 5, - }, - inRight: &YangType{ - Name: "bar", - Kind: Ydecimal64, - FractionDigits: 5, - }, - wantEqual: true, - }, { - name: "fraction-digits-unequal", - inLeft: &YangType{ - Name: "foo", - Kind: Ydecimal64, - FractionDigits: 5, - }, - inRight: &YangType{ - Name: "foo", - Kind: Ydecimal64, - FractionDigits: 4, - }, - wantEqual: false, - }, { - name: "types-unequal", - inLeft: &YangType{ - Name: "foo", - Kind: Ydecimal64, - FractionDigits: 5, - }, - inRight: &YangType{ - Name: "foo", - Kind: Yint64, - }, - wantEqual: false, - }, { - name: "defaults-equal", - inLeft: &YangType{ - Name: "foo", - Kind: Ystring, - Default: "bar", - HasDefault: true, - }, - inRight: &YangType{ - Name: "foo", - Kind: Ystring, - Default: "bar", - HasDefault: true, - }, - wantEqual: true, - }, { - name: "defaults-unequal", - inLeft: &YangType{ - Name: "foo", - Kind: Ystring, - Default: "bar", - HasDefault: true, - }, - inRight: &YangType{ - Name: "foo", - Kind: Ystring, - Default: "baz", - HasDefault: true, - }, - wantEqual: false, - }, { - name: "has-default-unequal", - inLeft: &YangType{ - Name: "foo", - Kind: Ystring, - Default: "", - }, - inRight: &YangType{ - Name: "foo", - Kind: Ystring, - Default: "", - HasDefault: true, - }, - wantEqual: false, - }} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if gotEqual := tt.inLeft.Equal(tt.inRight); gotEqual != tt.wantEqual { - t.Errorf("gotEqual: %v, wantEqual: %v", gotEqual, tt.wantEqual) - } - // Must be symmetric - if reverseEqual := tt.inRight.Equal(tt.inLeft); reverseEqual != tt.wantEqual { - t.Errorf("got reverseEqual: %v, wantEqual: %v", reverseEqual, tt.wantEqual) - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/build_yang.go b/src/webui/internal/goyang/pkg/yangentry/build_yang.go deleted file mode 100644 index 486f7299d..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/build_yang.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2020 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package yangentry contains high-level helpers for using yang.Entry objects. -package yangentry - -import ( - "fmt" - - "github.com/openconfig/goyang/pkg/yang" -) - -// Parse takes a list of either module/submodule names or .yang file -// paths, and a list of include paths. It runs the yang parser on the YANG -// files by searching for them in the include paths or in the current -// directory, returning a slice of yang.Entry pointers which represent the -// parsed top level modules. It also returns a list of errors encountered while -// parsing, if any. -func Parse(yangfiles, path []string) (map[string]*yang.Entry, []error) { - return parse(yangfiles, path, yang.NewModules()) -} - -// ParseWithOptions takes a list of either module/submodule names or .yang file -// paths, a list of include paths, and a set of parse options. It configures the -// yang parser with the specified parse options and runs it on the YANG -// files by searching for them in the include paths or in the current -// directory, returning a slice of yang.Entry pointers which represent the -// parsed top level modules. It also returns a list of errors encountered while -// parsing, if any. -func ParseWithOptions(yangfiles, path []string, parseOptions yang.Options) (map[string]*yang.Entry, []error) { - ms := yang.NewModules() - ms.ParseOptions = parseOptions - - return parse(yangfiles, path, ms) -} - -func parse(yangfiles, path []string, ms *yang.Modules) (map[string]*yang.Entry, []error) { - for _, p := range path { - ms.AddPath(fmt.Sprintf("%s/...", p)) - } - - var processErr []error - for _, name := range yangfiles { - if name == "" { - continue - } - if err := ms.Read(name); err != nil { - processErr = append(processErr, err) - } - } - - if len(processErr) > 0 { - return nil, processErr - } - - if errs := ms.Process(); len(errs) != 0 { - return nil, errs - } - - entries := make(map[string]*yang.Entry) - for _, m := range ms.Modules { - e := yang.ToEntry(m) - entries[e.Name] = e - } - - return entries, nil -} diff --git a/src/webui/internal/goyang/pkg/yangentry/build_yang_test.go b/src/webui/internal/goyang/pkg/yangentry/build_yang_test.go deleted file mode 100644 index 266df9cd0..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/build_yang_test.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2020 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package yangentry - -import ( - "testing" - - "github.com/openconfig/goyang/pkg/yang" -) - -// TestParse tests the Parse function - which takes an input -// set of modules and processes them using the goyang compiler into a set of -// yang.Entry pointers. -func TestParse(t *testing.T) { - tests := []struct { - name string - inFiles []string - inPath []string - wantErr bool - wantMods []string - }{{ - name: "simple valid module", - inFiles: []string{"testdata/00-valid-module.yang"}, - inPath: []string{"testdata"}, - wantMods: []string{"test-module"}, - }, { - name: "simple valid module without .yang extension", - inFiles: []string{"00-valid-module"}, - inPath: []string{"testdata"}, - wantMods: []string{"test-module"}, - }, { - name: "simple invalid module", - inFiles: []string{"testdata/01-invalid-module.yang"}, - inPath: []string{"testdata"}, - wantErr: true, - }, { - name: "valid import", - inFiles: []string{"testdata/02-valid-import.yang"}, - inPath: []string{"testdata/subdir"}, - wantMods: []string{"test-module"}, - }, { - name: "invalid import", - inFiles: []string{"testdata/03-invalid-import.yang"}, - inPath: []string{}, - wantErr: true, - }, { - name: "two modules", - inFiles: []string{"testdata/04-valid-module-one.yang", "testdata/04-valid-module-two.yang"}, - inPath: []string{}, - wantMods: []string{"module-one", "module-two"}, - }, { - name: "circular submodule dependency", - inFiles: []string{"testdata/05-circular-main.yang"}, - inPath: []string{"testdata/subdir"}, - wantErr: true, - }} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - entries, errs := Parse(tt.inFiles, tt.inPath) - if len(errs) != 0 && !tt.wantErr { - t.Fatalf("%s: unexpected error processing modules: %v", tt.name, errs) - } - - for _, m := range tt.wantMods { - if _, ok := entries[m]; !ok { - t.Fatalf("%s: could not find module %s", tt.name, m) - } - } - }) - } -} - -// TestParseWithOptions tests the ParseWithOptions function - which takes an input -// set of modules along with a set of parse options, and processes them using the goyang -// compiler into a set of yang.Entry pointers. -func TestParseWithOptions(t *testing.T) { - tests := []struct { - name string - inFiles []string - inPath []string - parseOptions yang.Options - wantErr bool - wantMods []string - }{ - { - name: "circular submodule dependency with default options", - inFiles: []string{"testdata/05-circular-main.yang"}, - inPath: []string{"testdata/subdir"}, - parseOptions: yang.Options{}, - wantErr: true, - }, - { - name: "circular submodule dependency with IgnoreSubmoduleCircularDependencies", - inFiles: []string{"testdata/05-circular-main.yang"}, - inPath: []string{"testdata/subdir"}, - parseOptions: yang.Options{IgnoreSubmoduleCircularDependencies: true}, - wantMods: []string{"circular-main"}, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - entries, errs := ParseWithOptions(tt.inFiles, tt.inPath, tt.parseOptions) - if len(errs) != 0 && !tt.wantErr { - t.Fatalf("%s: unexpected error processing modules: %v", tt.name, errs) - } - - for _, m := range tt.wantMods { - if _, ok := entries[m]; !ok { - t.Fatalf("%s: could not find module %s", tt.name, m) - } - } - }) - } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/00-valid-module.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/00-valid-module.yang deleted file mode 100644 index b216af121..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/00-valid-module.yang +++ /dev/null @@ -1,6 +0,0 @@ -module test-module { - prefix "t"; - namespace "urn:t"; - - leaf valid-leaf { type string; } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/01-invalid-module.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/01-invalid-module.yang deleted file mode 100644 index 7e28c9048..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/01-invalid-module.yang +++ /dev/null @@ -1,6 +0,0 @@ -module test-module { - prefix "t"; - namespace "urn:t"; - - leaf invalid-leaf { } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/02-valid-import.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/02-valid-import.yang deleted file mode 100644 index 621a1070c..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/02-valid-import.yang +++ /dev/null @@ -1,6 +0,0 @@ -module test-module { - prefix "t"; - namespace "urn:t"; - - import imported { prefix "i"; } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/03-invalid-import.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/03-invalid-import.yang deleted file mode 100644 index 65ba27a53..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/03-invalid-import.yang +++ /dev/null @@ -1,6 +0,0 @@ -module test-module { - prefix "t"; - namespace "urn:t"; - - import import-not-found { prefix "i"; } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-one.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-one.yang deleted file mode 100644 index 28ed7702e..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-one.yang +++ /dev/null @@ -1,6 +0,0 @@ -module module-one { - prefix "t"; - namespace "urn:t"; - - leaf one { type int8; } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-two.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-two.yang deleted file mode 100644 index d17cbccdd..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/04-valid-module-two.yang +++ /dev/null @@ -1,6 +0,0 @@ -module module-two { - prefix "t"; - namespace "urn:t"; - - leaf two { type int8; } -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/05-circular-main.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/05-circular-main.yang deleted file mode 100644 index d27bfcf6d..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/05-circular-main.yang +++ /dev/null @@ -1,12 +0,0 @@ -module circular-main { - yang-version "1.1"; - - namespace "urn:test:circular:main"; - - prefix "main"; - - include circular-sub-one; - include circular-sub-two; - - revision "2025-02-15"; -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-one.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-one.yang deleted file mode 100644 index cf50a8b3f..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-one.yang +++ /dev/null @@ -1,9 +0,0 @@ -submodule circular-sub-one { - yang-version "1.1"; - - belongs-to circular-main { prefix "main"; } - - include circular-sub-two; - - revision "2025-02-15"; -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-two.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-two.yang deleted file mode 100644 index afaf4c4ef..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/circular-sub-two.yang +++ /dev/null @@ -1,9 +0,0 @@ -submodule circular-sub-two { - yang-version "1.1"; - - belongs-to circular-main { prefix "main"; } - - include circular-sub-one; - - revision "2025-02-15"; -} diff --git a/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/imported.yang b/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/imported.yang deleted file mode 100644 index 71ddd9330..000000000 --- a/src/webui/internal/goyang/pkg/yangentry/testdata/subdir/imported.yang +++ /dev/null @@ -1,5 +0,0 @@ -module imported { - prefix "imported"; - namespace "urn:i"; - -} diff --git a/src/webui/internal/goyang/testdata/aug.yang b/src/webui/internal/goyang/testdata/aug.yang deleted file mode 100644 index 785a1a1cd..000000000 --- a/src/webui/internal/goyang/testdata/aug.yang +++ /dev/null @@ -1,28 +0,0 @@ -module aug { - namespace "yang-sucks"; - prefix "yang"; - grouping bgp-neighbor_config { - leaf peer-as { type string; } - } - grouping bgp-neighbors { - list neighbor { - uses bgp-neighbor-group; - } - } - grouping bgp-neighbor-group { - container config { - uses bgp-neighbor_config; - } - } - grouping bgp-neighbor-peer-group_config { - leaf peer-group { type string; } - } - augment /bgp/neighbors/neighbor/config { - uses bgp-neighbor-peer-group_config; - } - container bgp { - container neighbors { - uses bgp-neighbors; - } - } -} diff --git a/src/webui/internal/goyang/testdata/base.yang b/src/webui/internal/goyang/testdata/base.yang deleted file mode 100644 index 915631984..000000000 --- a/src/webui/internal/goyang/testdata/base.yang +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Base test yang module. -module base { - namespace "urn:mod"; - prefix "base"; - - include sub; - import other { - prefix bother; - } - - // basic type tests - typedef base-type { type int32; } - leaf base-leaf1 { type base-type; } - leaf base-leaf2 { type base:base-type; } - leaf base-leaf3 { type bother:other-type; } - leaf base-leaf4 { type sub-type; } - - grouping base-group { - description - "The base-group is used to test the 'uses' statement below. - This description is here to simply include a multi-line string - as an example of multi-line strings"; - leaf base-group-leaf { - config false; - type string; - } - } - - // test uses and leaf ref - container base-container-1 { - uses base-group; - uses bother:other-group; - uses base:sub-group; - choice base-choice { - case choice-a { - leaf base-choice-a1 { type string; } - leaf base-choice-a2 { - type leafref { path ../base-container-1-leaf; } - } - } - case choice-b { - leaf base-choice-b1 { type string; } - leaf base-choice-b2 { - type leafref { path ../../base-container-2/base-container-2a/base-container-2a-leaf; } - } - } - } - leaf base-container-1-leaf { type string; } - } - - // container referenced by a leafref above - container base-container-2 { - container base-container-2a { - leaf base-container-2a-leaf { type string; } - } - } - - // test basic augmenting - augment /base-container-1/base-choice/choice-a { - leaf base-choice-a3 { type string; } - } - augment /base-container-1/base-choice { - case choice-c { - leaf base-choice-c1 { type string; } - } - } - - // simple extension test - extension base-ext { - argument base-arg; - } - container ext-container { - config false; - leaf ext-container-leaf { type string; } - base:base-ext "EXTENSION" { - leaf base-ext-leaf { type string; } - } - } -} diff --git a/src/webui/internal/goyang/testdata/other.yang b/src/webui/internal/goyang/testdata/other.yang deleted file mode 100644 index acbe95a44..000000000 --- a/src/webui/internal/goyang/testdata/other.yang +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// imported by base.yang. -module other { - namespace "uri:empty"; - prefix "otherp"; - typedef other-type { type string; } - - // This container should not appear in base, even though this file is - // imported by base. That is just the YANG is defined. - container other-container { - leaf other-container-leaf1 { type other-type; } - leaf other-container-leaf2 { type otherp:other-type; } - } - grouping other-group { - leaf other-group-leaf { type string; } - } -} diff --git a/src/webui/internal/goyang/testdata/sub.yang b/src/webui/internal/goyang/testdata/sub.yang deleted file mode 100644 index 940920c67..000000000 --- a/src/webui/internal/goyang/testdata/sub.yang +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// included by base.yang. -submodule sub { - belongs-to base { prefix "sbase"; } - typedef sub-type { type sub-type2; } - typedef sub-type2 { type int8; } - container sub-container { - leaf sub-container-leaf { type sub-type; } - } - grouping sub-group { - leaf sub-group-leaf { type string; } - } -} diff --git a/src/webui/internal/goyang/testdata/subdir/subdir1.yang b/src/webui/internal/goyang/testdata/subdir/subdir1.yang deleted file mode 100644 index c6a8523b8..000000000 --- a/src/webui/internal/goyang/testdata/subdir/subdir1.yang +++ /dev/null @@ -1,12 +0,0 @@ -// A YANG module located in a subdirectory, to test the AddYANGPaths -// helper function. - -module subdir1 { - yang-version "1"; - -namespace "namespace:goes:here"; - prefix "subdir1"; - - description - "This module is to be found by test cases."; -} diff --git a/src/webui/internal/goyang/tree.go b/src/webui/internal/goyang/tree.go deleted file mode 100644 index 7ba17c8b9..000000000 --- a/src/webui/internal/goyang/tree.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - "io" - "sort" - - "github.com/openconfig/goyang/pkg/indent" - "github.com/openconfig/goyang/pkg/yang" -) - -func init() { - register(&formatter{ - name: "tree", - f: doTree, - help: "display in a tree format", - }) -} - -func doTree(w io.Writer, entries []*yang.Entry) { - for _, e := range entries { - Write(w, e) - } -} - -// Write writes e, formatted, and all of its children, to w. -func Write(w io.Writer, e *yang.Entry) { - if e.Description != "" { - fmt.Fprintln(w) - fmt.Fprintln(indent.NewWriter(w, "// "), e.Description) - } - if len(e.Exts) > 0 { - fmt.Fprintf(w, "extensions: {\n") - for _, ext := range e.Exts { - if n := ext.NName(); n != "" { - fmt.Fprintf(w, " %s %s;\n", ext.Kind(), n) - } else { - fmt.Fprintf(w, " %s;\n", ext.Kind()) - } - } - fmt.Fprintln(w, "}") - } - switch { - case e.RPC != nil: - fmt.Fprintf(w, "RPC: ") - case e.ReadOnly(): - fmt.Fprintf(w, "RO: ") - default: - fmt.Fprintf(w, "rw: ") - } - if e.Type != nil { - fmt.Fprintf(w, "%s ", getTypeName(e)) - } - name := e.Name - if e.Prefix != nil { - name = e.Prefix.Name + ":" + name - } - switch { - case e.Dir == nil && e.ListAttr != nil: - fmt.Fprintf(w, "[]%s\n", name) - return - case e.Dir == nil: - fmt.Fprintf(w, "%s\n", name) - return - case e.ListAttr != nil: - fmt.Fprintf(w, "[%s]%s {\n", e.Key, name) //} - default: - fmt.Fprintf(w, "%s {\n", name) //} - } - if r := e.RPC; r != nil { - if r.Input != nil { - Write(indent.NewWriter(w, " "), r.Input) - } - if r.Output != nil { - Write(indent.NewWriter(w, " "), r.Output) - } - } - var names []string - for k := range e.Dir { - names = append(names, k) - } - sort.Strings(names) - for _, k := range names { - Write(indent.NewWriter(w, " "), e.Dir[k]) - } - // { to match the brace below to keep brace matching working - fmt.Fprintln(w, "}") -} - -func getTypeName(e *yang.Entry) string { - if e == nil || e.Type == nil { - return "" - } - // Return our root's type name. - // This is should be the builtin type-name - // for this entry. - return e.Type.Root.Name -} diff --git a/src/webui/internal/goyang/types.go b/src/webui/internal/goyang/types.go deleted file mode 100644 index 864e59ed8..000000000 --- a/src/webui/internal/goyang/types.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - "io" - "strings" - - "github.com/openconfig/goyang/pkg/indent" - "github.com/openconfig/goyang/pkg/yang" - "github.com/pborman/getopt" -) - -var ( - typesDebug bool - typesVerbose bool -) - -func init() { - flags := getopt.New() - register(&formatter{ - name: "types", - f: doTypes, - help: "display found types", - flags: flags, - }) - flags.BoolVarLong(&typesDebug, "types_debug", 0, "display debug information") - flags.BoolVarLong(&typesVerbose, "types_verbose", 0, "include base information") -} - -func doTypes(w io.Writer, entries []*yang.Entry) { - types := Types{} - for _, e := range entries { - types.AddEntry(e) - } - - for t := range types { - printType(w, t, typesVerbose) - } - if typesDebug { - for _, e := range entries { - showall(w, e) - } - } -} - -// Types keeps track of all the YangTypes defined. -type Types map[*yang.YangType]struct{} - -// AddEntry adds all types defined in e and its descendants to t. -func (t Types) AddEntry(e *yang.Entry) { - if e == nil { - return - } - if e.Type != nil { - t[e.Type.Root] = struct{}{} - } - for _, d := range e.Dir { - t.AddEntry(d) - } -} - -// printType prints type t in a moderately human readable format to w. -func printType(w io.Writer, t *yang.YangType, verbose bool) { - if verbose && t.Base != nil { - base := yang.Source(t.Base) - if base == "unknown" { - base = "unnamed type" - } - fmt.Fprintf(w, "%s: ", base) - } - fmt.Fprintf(w, "%s", t.Root.Name) - if t.Kind.String() != t.Root.Name { - fmt.Fprintf(w, "(%s)", t.Kind) - } - if t.Units != "" { - fmt.Fprintf(w, " units=%s", t.Units) - } - if t.Default != "" { - fmt.Fprintf(w, " default=%q", t.Default) - } - if t.FractionDigits != 0 { - fmt.Fprintf(w, " fraction-digits=%d", t.FractionDigits) - } - if len(t.Length) > 0 { - fmt.Fprintf(w, " length=%s", t.Length) - } - if t.Kind == yang.YinstanceIdentifier && !t.OptionalInstance { - fmt.Fprintf(w, " required") - } - if t.Kind == yang.Yleafref && t.Path != "" { - fmt.Fprintf(w, " path=%q", t.Path) - } - if len(t.Pattern) > 0 { - fmt.Fprintf(w, " pattern=%s", strings.Join(t.Pattern, "|")) - } - b := yang.BaseTypedefs[t.Kind.String()].YangType - if len(t.Range) > 0 && !t.Range.Equal(b.Range) { - fmt.Fprintf(w, " range=%s", t.Range) - } - if len(t.Type) > 0 { - fmt.Fprintf(w, "{\n") - for _, t := range t.Type { - printType(indent.NewWriter(w, " "), t, verbose) - } - fmt.Fprintf(w, "}") - } - fmt.Fprintf(w, ";\n") -} - -func showall(w io.Writer, e *yang.Entry) { - if e == nil { - return - } - if e.Type != nil { - fmt.Fprintf(w, "\n%s\n ", e.Node.Statement().Location()) - printType(w, e.Type.Root, false) - } - for _, d := range e.Dir { - showall(w, d) - } -} diff --git a/src/webui/internal/goyang/yang.go b/src/webui/internal/goyang/yang.go deleted file mode 100644 index 548263279..000000000 --- a/src/webui/internal/goyang/yang.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Program yang parses YANG files, displays errors, and possibly writes -// something related to the input on output. -// -// Usage: yang [--path DIR] [--format FORMAT] [FORMAT OPTIONS] [MODULE] [FILE ...] -// -// If MODULE is specified (an argument that does not end in .yang), it is taken -// as the name of the module to display. Any FILEs specified are read, and the -// tree for MODULE is displayed. If MODULE was not defined in FILEs (or no -// files were specified), then the file MODULES.yang is read as well. An error -// is displayed if no definition for MODULE was found. -// -// If MODULE is missing, then all base modules read from the FILEs are -// displayed. If there are no arguments then standard input is parsed. -// -// If DIR is specified, it is considered a comma separated list of paths -// to append to the search directory. If DIR appears as DIR/... then -// DIR and all direct and indirect subdirectories are checked. -// -// FORMAT, which defaults to "tree", specifies the format of output to produce. -// Use "goyang --help" for a list of available formats. -// -// FORMAT OPTIONS are flags that apply to a specific format. They must follow -// --format. -// -// THIS PROGRAM IS STILL JUST A DEVELOPMENT TOOL. -package main - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "runtime/trace" - "sort" - "strings" - - "github.com/openconfig/goyang/pkg/indent" - "github.com/openconfig/goyang/pkg/yang" - "github.com/pborman/getopt" -) - -// Each format must register a formatter with register. The function f will -// be called once with the set of yang Entry trees generated. -type formatter struct { - name string - f func(io.Writer, []*yang.Entry) - help string - flags *getopt.Set -} - -var formatters = map[string]*formatter{} - -func register(f *formatter) { - formatters[f.name] = f -} - -// exitIfError writes errs to standard error and exits with an exit status of 1. -// If errs is empty then exitIfError does nothing and simply returns. -func exitIfError(errs []error) { - if len(errs) > 0 { - for _, err := range errs { - fmt.Fprintln(os.Stderr, err) - } - stop(1) - } -} - -var stop = os.Exit - -func main() { - var format string - formats := make([]string, 0, len(formatters)) - for k := range formatters { - formats = append(formats, k) - } - sort.Strings(formats) - - var traceP string - var help bool - var paths []string - var ignoreSubmoduleCircularDependencies bool - getopt.ListVarLong(&paths, "path", 'p', "comma separated list of directories to add to search path", "DIR[,DIR...]") - getopt.StringVarLong(&format, "format", 'f', "format to display: "+strings.Join(formats, ", "), "FORMAT") - getopt.StringVarLong(&traceP, "trace", 't', "write trace into to TRACEFILE", "TRACEFILE") - getopt.BoolVarLong(&help, "help", 'h', "display help") - getopt.BoolVarLong(&ignoreSubmoduleCircularDependencies, "ignore-circdep", 'g', "ignore circular dependencies between submodules") - getopt.SetParameters("[FORMAT OPTIONS] [SOURCE] [...]") - - if err := getopt.Getopt(func(o getopt.Option) bool { - if o.Name() == "--format" { - f, ok := formatters[format] - if !ok { - fmt.Fprintf(os.Stderr, "%s: invalid format. Choices are %s\n", format, strings.Join(formats, ", ")) - stop(1) - } - if f.flags != nil { - f.flags.VisitAll(func(o getopt.Option) { - getopt.AddOption(o) - }) - } - } - return true - }); err != nil { - fmt.Fprintln(os.Stderr, err) - getopt.PrintUsage(os.Stderr) - os.Exit(1) - } - - if traceP != "" { - fp, err := os.Create(traceP) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - trace.Start(fp) - stop = func(c int) { trace.Stop(); os.Exit(c) } - defer func() { trace.Stop() }() - } - - if help { - getopt.CommandLine.PrintUsage(os.Stderr) - fmt.Fprintf(os.Stderr, ` -SOURCE may be a module name or a .yang file. - -Formats: -`) - for _, fn := range formats { - f := formatters[fn] - fmt.Fprintf(os.Stderr, " %s - %s\n", f.name, f.help) - if f.flags != nil { - f.flags.PrintOptions(indent.NewWriter(os.Stderr, " ")) - } - fmt.Fprintln(os.Stderr) - } - stop(0) - } - - ms := yang.NewModules() - ms.ParseOptions.IgnoreSubmoduleCircularDependencies = ignoreSubmoduleCircularDependencies - - for _, path := range paths { - expanded, err := yang.PathsWithModules(path) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - ms.AddPath(expanded...) - } - - if format == "" { - format = "tree" - } - if _, ok := formatters[format]; !ok { - fmt.Fprintf(os.Stderr, "%s: invalid format. Choices are %s\n", format, strings.Join(formats, ", ")) - stop(1) - - } - - files := getopt.Args() - - if len(files) == 0 { - data, err := ioutil.ReadAll(os.Stdin) - if err == nil { - err = ms.Parse(string(data), "") - } - if err != nil { - fmt.Fprintln(os.Stderr, err) - stop(1) - } - } - - for _, name := range files { - if err := ms.Read(name); err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - } - - // Process the read files, exiting if any errors were found. - exitIfError(ms.Process()) - - // Keep track of the top level modules we read in. - // Those are the only modules we want to print below. - mods := map[string]*yang.Module{} - var names []string - - for _, m := range ms.Modules { - if mods[m.Name] == nil { - mods[m.Name] = m - names = append(names, m.Name) - } - } - sort.Strings(names) - entries := make([]*yang.Entry, len(names)) - for x, n := range names { - entries[x] = yang.ToEntry(mods[n]) - } - - formatters[format].f(os.Stdout, entries) -} diff --git a/src/webui/vendor/github.com/openconfig/goyang/pkg/yang/yang.go b/src/webui/vendor/github.com/openconfig/goyang/pkg/yang/yang.go index fad667e25..e9b769177 100644 --- a/src/webui/vendor/github.com/openconfig/goyang/pkg/yang/yang.go +++ b/src/webui/vendor/github.com/openconfig/goyang/pkg/yang/yang.go @@ -626,12 +626,12 @@ type Uses struct { Extensions []*Statement `yang:"Ext" json:"-"` Augment []*Augment `yang:"augment" json:",omitempty"` - Description *Value `yang:"description" json:",omitempty"` - IfFeature []*Value `yang:"if-feature" json:"-"` - Refine []*Refine `yang:"refine" json:"-"` - Reference *Value `yang:"reference" json:"-"` - Status *Value `yang:"status" json:"-"` - When *Value `yang:"when" json:",omitempty"` + Description *Value `yang:"description" json:",omitempty"` + IfFeature []*Value `yang:"if-feature" json:"-"` + Refine []*Refine `yang:"refine" json:"-"` + Reference *Value `yang:"reference" json:"-"` + Status *Value `yang:"status" json:"-"` + When *Value `yang:"when" json:",omitempty"` } func (Uses) Kind() string { return "uses" } diff --git a/src/webui/vendor/modules.txt b/src/webui/vendor/modules.txt index 5e37e86d2..dc92de823 100644 --- a/src/webui/vendor/modules.txt +++ b/src/webui/vendor/modules.txt @@ -5,10 +5,8 @@ github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value -# github.com/openconfig/goyang v1.6.3 => ./internal/goyang +# github.com/openconfig/goyang v1.6.3 => github.com/kernelkit/goyang v1.6.4-0.20260617163501-afcacf84230c ## explicit; go 1.22.0 github.com/openconfig/goyang/pkg/indent github.com/openconfig/goyang/pkg/yang -# github.com/pborman/getopt v1.1.0 -## explicit; go 1.13 -# github.com/openconfig/goyang => ./internal/goyang +# github.com/openconfig/goyang => github.com/kernelkit/goyang v1.6.4-0.20260617163501-afcacf84230c From a365238854e916d0110ceb6f68cdcb7ccf25373a Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 17 Jun 2026 20:06:02 +0200 Subject: [PATCH 2/2] .github: scan Go modules for known vulnerabilities Add govulncheck CI and Dependabot for the two Go modules, src/webui and src/netbrowse, so vendored dependencies don't quietly accumulate CVEs between manual updates. The workflow reports every finding in the run summary but only fails on vulnerabilities our code actually calls in a dependency. Called stdlib vulnerabilities are surfaced too, but they're fixed by bumping the Buildroot host Go rather than a module's go.mod, so they don't gate the build. Dependabot ignores openconfig/goyang: it's pinned to our kernelkit fork via a replace directive and stepped by hand. Signed-off-by: Joachim Wiberg --- .github/dependabot.yml | 15 +++++++ .github/workflows/govulncheck.yml | 68 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/govulncheck.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..075307dea --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: /src/webui + schedule: + interval: weekly + # goyang is pinned to our kernelkit fork via a replace directive and + # stepped by hand when we add patches; leave it for Dependabot to ignore. + ignore: + - dependency-name: github.com/openconfig/goyang + + - package-ecosystem: gomod + directory: /src/netbrowse + schedule: + interval: weekly diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml new file mode 100644 index 000000000..8fcc6b248 --- /dev/null +++ b/.github/workflows/govulncheck.yml @@ -0,0 +1,68 @@ +name: Go Vulnerability Scan + +on: + push: + branches: + - main + paths: + - 'src/webui/**' + - 'src/netbrowse/**' + - '.github/workflows/govulncheck.yml' + pull_request: + paths: + - 'src/webui/**' + - 'src/netbrowse/**' + - '.github/workflows/govulncheck.yml' + schedule: + - cron: '5 0 * * 6' # Saturday at 00:05 UTC, same as Coverity + workflow_dispatch: + +jobs: + govulncheck: + if: ${{ github.repository_owner == 'kernelkit' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + module: + - src/webui + - src/netbrowse + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-go@v6 + with: + go-version: stable + + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@latest + + - name: Scan ${{ matrix.module }} + working-directory: ${{ matrix.module }} + run: | + # Full report, for the run summary. govulncheck exits non-zero + # whenever it finds anything, so don't let it fail the step here. + { + echo "## govulncheck: ${{ matrix.module }}" + echo '```' + govulncheck ./... || true + echo '```' + } | tee -a "$GITHUB_STEP_SUMMARY" + + # Gate on vulnerabilities reachable from our code through a + # dependency. govulncheck's call-graph analysis is transitive, + # so indirect use counts too (we call a dep that calls the bad + # symbol). trace[0] is the vulnerable symbol; we key on the + # module it lives in. A chain that bottoms out in stdlib is + # fixed by bumping the Buildroot host Go, not this module's + # go.mod, so it's reported above but doesn't fail the build. + # Keep the json scan and jq unguarded so a tool failure fails the + # gate closed; only grep's no-match exit (all-clear) is tolerated. + govulncheck -format json ./... > scan.json || true + called=$(jq -r 'select(.finding.trace[0].function != null) | + .finding.trace[0].module' scan.json | sort -u) + vulns=$(printf '%s' "$called" | grep -vx stdlib || true) + if [ -n "$vulns" ]; then + echo "::error::Called vulnerabilities in dependencies: $(echo "$vulns" | paste -sd, -)" + exit 1 + fi