Skip to content

Commit

Permalink
Add go yaml validations (part 1) (GoogleCloudPlatform#11621)
Browse files Browse the repository at this point in the history
  • Loading branch information
zli82016 authored Sep 3, 2024
1 parent 9754bac commit b7381a6
Show file tree
Hide file tree
Showing 17 changed files with 204 additions and 184 deletions.
71 changes: 14 additions & 57 deletions mmv1/api/async.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
package api

import (
"log"
"strings"

"github.com/GoogleCloudPlatform/magic-modules/mmv1/google"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
)

// Base class from which other Async classes can inherit.
Expand All @@ -40,13 +40,6 @@ type Async struct {
PollAsync `yaml:",inline"`
}

// def validate
// super

// check :operation, type: Operation
// check :actions, default: %w[create delete update], type: ::Array, item_type: ::String
// end

// def allow?(method)
func (a Async) Allow(method string) bool {
return slices.Contains(a.Actions, strings.ToLower(method))
Expand Down Expand Up @@ -80,11 +73,6 @@ func NewAsync() *Async {
return oa
}

// def validate
// super
// check :resource_inside_response, type: :boolean, default: false
// end

// Represents an asynchronous operation definition
type OpAsync struct {
Result OpAsyncResult
Expand All @@ -106,17 +94,6 @@ type OpAsync struct {
// @error = error
// end

// def validate
// super

// check :operation, type: Operation, required: true
// check :result, type: Result, default: Result.new
// check :status, type: Status
// check :error, type: Error
// check :actions, default: %w[create delete update], type: ::Array, item_type: ::String
// check :include_project, type: :boolean, default: false
// end

type OpAsyncOperation struct {
Kind string

Expand Down Expand Up @@ -156,12 +133,6 @@ type OpAsyncResult struct {
// @resource_inside_response = resource_inside_response
// end

// def validate
// super

// check :path, type: String
// end

// Provides information to parse the result response to check operation
// status
type OpAsyncStatus struct {
Expand All @@ -181,12 +152,6 @@ type OpAsyncStatus struct {
// @allowed = allowed
// end

// def validate
// super
// check :path, type: String
// check :allowed, type: Array, item_type: [::String, :boolean]
// end

// Provides information on how to retrieve errors of the executed operations
type OpAsyncError struct {
google.YamlValidator
Expand All @@ -202,12 +167,6 @@ type OpAsyncError struct {
// @message = message
// end

// def validate
// super
// check :path, type: String
// check :message, type: String
// end

// Async implementation for polling in Terraform
type PollAsync struct {
// Details how to poll for an eventually-consistent resource state.
Expand All @@ -233,12 +192,12 @@ type PollAsync struct {
TargetOccurrences int `yaml:"target_occurrences"`
}

func (a *Async) UnmarshalYAML(n *yaml.Node) error {
func (a *Async) UnmarshalYAML(unmarshal func(any) error) error {
a.Actions = []string{"create", "delete", "update"}
type asyncAlias Async
aliasObj := (*asyncAlias)(a)

err := n.Decode(&aliasObj)
err := unmarshal(aliasObj)
if err != nil {
return err
}
Expand All @@ -250,16 +209,14 @@ func (a *Async) UnmarshalYAML(n *yaml.Node) error {
return nil
}

// return nil
// }

// def validate
// super

// check :check_response_func_existence, type: String, required: true
// check :check_response_func_absence, type: String,
// default: 'transport_tpg.PollCheckForAbsence'
// check :custom_poll_read, type: String
// check :suppress_error, type: :boolean, default: false
// check :target_occurrences, type: Integer, default: 1
// end
func (a *Async) Validate() {
if a.Type == "OpAsync" {
if a.Operation == nil {
log.Fatalf("Missing `Operation` for OpAsync")
} else {
if a.Operation.BaseUrl != "" && a.Operation.FullUrl != "" {
log.Fatalf("`base_url` and `full_url` cannot be set at the same time in OpAsync operation.")
}
}
}
}
51 changes: 25 additions & 26 deletions mmv1/api/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ package api
import (
"log"
"strings"

"gopkg.in/yaml.v3"
"unicode"

"github.com/GoogleCloudPlatform/magic-modules/mmv1/api/product"
"github.com/GoogleCloudPlatform/magic-modules/mmv1/google"
Expand Down Expand Up @@ -68,12 +67,11 @@ type Product struct {
ClientName string `yaml:"client_name"`
}

func (p *Product) UnmarshalYAML(n *yaml.Node) error {
func (p *Product) UnmarshalYAML(unmarshal func(any) error) error {
type productAlias Product
aliasObj := (*productAlias)(p)

err := n.Decode(&aliasObj)
if err != nil {
if err := unmarshal(aliasObj); err != nil {
return err
}

Expand All @@ -84,31 +82,32 @@ func (p *Product) UnmarshalYAML(n *yaml.Node) error {
}

func (p *Product) Validate() {
// TODO Q2 Rewrite super
// super
}

// def validate
// super
// set_variables @objects, :__product
// product names must start with a capital
for i, ch := range p.Name {
if !unicode.IsUpper(ch) {
log.Fatalf("product name `%s` must start with a capital letter.", p.Name)
}
if i == 0 {
break
}
}

// // name comes from Named, and product names must start with a capital
// caps = ('A'..'Z').to_a
// unless caps.include? @name[0]
// raise "product name `//{@name}` must start with a capital letter."
// end
if len(p.Scopes) == 0 {
log.Fatalf("Missing `scopes` for product %s", p.Name)
}

// check :display_name, type: String
// check :objects, type: Array, item_type: Api::Resource
// check :scopes, type: Array, item_type: String, required: true
// check :operation_retry, type: String
if p.Versions == nil {
log.Fatalf("Missing `versions` for product %s", p.Name)
}

// check :async, type: Api::Async
// check :legacy_name, type: String
// check :client_name, type: String
for _, v := range p.Versions {
v.Validate(p.Name)
}

// check :versions, type: Array, item_type: Api::Product::Version, required: true
// end
if p.Async != nil {
p.Async.Validate()
}
}

// ====================
// Custom Setters
Expand Down
16 changes: 10 additions & 6 deletions mmv1/api/product/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package product

import (
"log"

"golang.org/x/exp/slices"
)

Expand All @@ -40,12 +42,14 @@ type Version struct {
Name string
}

// def validate
// super
// check :cai_base_url, type: String, required: false
// check :base_url, type: String, required: true
// check :name, type: String, allowed: ORDER, required: true
// end
func (v *Version) Validate(pName string) {
if v.Name == "" {
log.Fatalf("Missing `name` in `version` for product %s", pName)
}
if v.BaseUrl == "" {
log.Fatalf("Missing `base_url` in `version` for product %s", pName)
}
}

// def to_s
// "//{name}: //{base_url}"
Expand Down
82 changes: 71 additions & 11 deletions mmv1/api/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ package api

import (
"fmt"
"log"
"maps"
"regexp"
"sort"
Expand All @@ -23,7 +24,6 @@ import (
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api/resource"
"github.com/GoogleCloudPlatform/magic-modules/mmv1/google"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
)

type Resource struct {
Expand Down Expand Up @@ -311,7 +311,7 @@ type Resource struct {
ImportPath string
}

func (r *Resource) UnmarshalYAML(n *yaml.Node) error {
func (r *Resource) UnmarshalYAML(unmarshal func(any) error) error {
r.CreateVerb = "POST"
r.ReadVerb = "GET"
r.DeleteVerb = "DELETE"
Expand All @@ -320,7 +320,7 @@ func (r *Resource) UnmarshalYAML(n *yaml.Node) error {
type resourceAlias Resource
aliasObj := (*resourceAlias)(r)

err := n.Decode(&aliasObj)
err := unmarshal(aliasObj)
if err != nil {
return err
}
Expand All @@ -331,6 +331,9 @@ func (r *Resource) UnmarshalYAML(n *yaml.Node) error {
if r.CollectionUrlKey == "" {
r.CollectionUrlKey = google.Camelize(google.Plural(r.Name), "lower")
}
if r.IdFormat == "" {
r.IdFormat = r.SelfLinkUri()
}

if len(r.VirtualFields) > 0 {
for _, f := range r.VirtualFields {
Expand All @@ -341,19 +344,76 @@ func (r *Resource) UnmarshalYAML(n *yaml.Node) error {
return nil
}

// TODO: rewrite functions
func (r *Resource) Validate() {
// TODO Q1 Rewrite super
// super
}

func (r *Resource) SetDefault(product *Product) {
r.ProductMetadata = product
for _, property := range r.AllProperties() {
property.SetDefault(r)
}
if r.IdFormat == "" {
r.IdFormat = r.SelfLinkUri()
}

func (r *Resource) Validate() {
if r.NestedQuery != nil && r.NestedQuery.IsListOfIds && len(r.Identity) != 1 {
log.Fatalf("`is_list_of_ids: true` implies resource has exactly one `identity` property")
}

// Ensures we have all properties defined
for _, i := range r.Identity {
hasIdentify := slices.ContainsFunc(r.AllUserProperties(), func(p *Type) bool {
return p.Name == i
})
if !hasIdentify {
log.Fatalf("Missing property/parameter for identity %s", i)
}
}

if r.Description == "" {
log.Fatalf("Missing `description` for resource %s", r.Name)
}

if !r.Exclude {
if len(r.Properties) == 0 {
log.Fatalf("Missing `properties` for resource %s", r.Name)
}
}

allowed := []string{"POST", "PUT", "PATCH"}
if !slices.Contains(allowed, r.CreateVerb) {
log.Fatalf("Value on `create_verb` should be one of %#v", allowed)
}

allowed = []string{"GET", "POST"}
if !slices.Contains(allowed, r.ReadVerb) {
log.Fatalf("Value on `read_verb` should be one of %#v", allowed)
}

allowed = []string{"POST", "PUT", "PATCH", "DELETE"}
if !slices.Contains(allowed, r.DeleteVerb) {
log.Fatalf("Value on `delete_verb` should be one of %#v", allowed)
}

allowed = []string{"POST", "PUT", "PATCH"}
if !slices.Contains(allowed, r.UpdateVerb) {
log.Fatalf("Value on `update_verb` should be one of %#v", allowed)
}

for _, property := range r.AllProperties() {
property.Validate(r.Name)
}

if r.IamPolicy != nil {
r.IamPolicy.Validate(r.Name)
}

if r.NestedQuery != nil {
r.NestedQuery.Validate(r.Name)
}

for _, example := range r.Examples {
example.Validate(r.Name)
}

if r.Async != nil {
r.Async.Validate()
}
}

Expand Down
Loading

0 comments on commit b7381a6

Please sign in to comment.