go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/aip/datamodel_builder.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package aip
    16  
    17  type ColumnBuilder struct {
    18  	column Column
    19  }
    20  
    21  // NewColumn starts building a new column.
    22  func NewColumn() *ColumnBuilder {
    23  	return &ColumnBuilder{Column{columnType: ColumnTypeString}}
    24  }
    25  
    26  // WithFieldPath specifies the field path the column maps to
    27  // in the returned resource. Field paths are described in AIP-161.
    28  //
    29  // For convenience, the field path is described here as a set of
    30  // segments where each segment is joined by the traversal operator (.).
    31  // E.g. the field path "metrics.`some-metric`.value" would be specified
    32  // as ["metrics", "some-metric", "value"].
    33  func (c *ColumnBuilder) WithFieldPath(segments ...string) *ColumnBuilder {
    34  	c.column.fieldPath = NewFieldPath(segments...)
    35  	return c
    36  }
    37  
    38  // WithDatabaseName specifies the database name of the column.
    39  // Important: Only pass safe values (e.g. compile-time constants) to this
    40  // field.
    41  // User input MUST NOT flow to this field, as it will be used directly
    42  // in SQL statements and would allow the user to perform SQL injection
    43  // attacks.
    44  func (c *ColumnBuilder) WithDatabaseName(name string) *ColumnBuilder {
    45  	c.column.databaseName = name
    46  	return c
    47  }
    48  
    49  // KeyValue specifies this column is an array of structs with two string members: key and value.
    50  // The key is exposed as a field on the column name, the value can be queried with :, = and !=
    51  // Example query: tag.key=value
    52  func (c *ColumnBuilder) KeyValue() *ColumnBuilder {
    53  	c.column.keyValue = true
    54  	return c
    55  }
    56  
    57  // Bool specifies this column has bool type in the database.
    58  func (c *ColumnBuilder) Bool() *ColumnBuilder {
    59  	c.column.columnType = ColumnTypeBool
    60  	return c
    61  }
    62  
    63  // Sortable specifies this column can be sorted on.
    64  func (c *ColumnBuilder) Sortable() *ColumnBuilder {
    65  	c.column.sortable = true
    66  	return c
    67  }
    68  
    69  // Filterable specifies this column can be filtered on.
    70  func (c *ColumnBuilder) Filterable() *ColumnBuilder {
    71  	c.column.filterable = true
    72  	return c
    73  }
    74  
    75  // FilterableImplicitly specifies this column can be filtered on implicitly.
    76  // This means that AIP-160 filter expressions not referencing any
    77  // particular field will try to search in this column.
    78  func (c *ColumnBuilder) FilterableImplicitly() *ColumnBuilder {
    79  	c.column.filterable = true
    80  	c.column.implicitFilter = true
    81  	return c
    82  }
    83  
    84  // WithArgumentSubstitutor specifies a substitution that should happen to the user-specified
    85  // filter argument before it is matched against the database value. If this option is enabled,
    86  // the filter operators permitted will be limited to = (equals) and != (not equals).
    87  func (c *ColumnBuilder) WithArgumentSubstitutor(f func(sub string) string) *ColumnBuilder {
    88  	c.column.argSubstitute = f
    89  	return c
    90  }
    91  
    92  // Build returns the built column.
    93  func (c *ColumnBuilder) Build() *Column {
    94  	result := &Column{}
    95  	*result = c.column
    96  	return result
    97  }
    98  
    99  type TableBuilder struct {
   100  	columns []*Column
   101  }
   102  
   103  // NewTable starts building a new table.
   104  func NewTable() *TableBuilder {
   105  	return &TableBuilder{}
   106  }
   107  
   108  // WithColumns specifies the columns in the table.
   109  func (t *TableBuilder) WithColumns(columns ...*Column) *TableBuilder {
   110  	t.columns = columns
   111  	return t
   112  }
   113  
   114  // Build returns the built table.
   115  func (t *TableBuilder) Build() *Table {
   116  	columnByFieldPath := make(map[string]*Column)
   117  	for _, c := range t.columns {
   118  		if _, ok := columnByFieldPath[c.fieldPath.String()]; ok {
   119  			panic("multiple columns with the same field path: " + c.fieldPath.String())
   120  		}
   121  		columnByFieldPath[c.fieldPath.String()] = c
   122  	}
   123  
   124  	return &Table{
   125  		columns:           t.columns,
   126  		columnByFieldPath: columnByFieldPath,
   127  	}
   128  }