[reland] JSON source generator includes error estimates
> Initially, an error estimate is included for each privacy level
> using a default population of 100K.
To prevent google3 test failure, unit tests now
construct an error calculator using a string containing
the testParamRecords rather than a file path.
Change-Id: I5e02f92b74363b0ecae70ce8db356ec9b7e6ab01
Reviewed-on: https://fuchsia-review.googlesource.com/c/cobalt/+/466454
Reviewed-by: Alexandre Zani <azani@google.com>
Commit-Queue: Jared Weinstein <jaredweinstein@google.com>
diff --git a/src/bin/config_parser/src/privacy/error_calculator.go b/src/bin/config_parser/src/privacy/error_calculator.go
index dfd6cf5..2dff690 100644
--- a/src/bin/config_parser/src/privacy/error_calculator.go
+++ b/src/bin/config_parser/src/privacy/error_calculator.go
@@ -11,7 +11,7 @@
)
type ErrorCalculator struct {
- calc PrivacyEncodingParamsCalculator
+ ParamsCalc PrivacyEncodingParamsCalculator
}
// Public factory method for creating an ErrorCalculator given a
@@ -20,6 +20,20 @@
return &ErrorCalculator{paramsCalc}
}
+// Public factory method for creating an ErrorCalculator given the file path
+// of the PrivacyEncodingParams.
+func NewErrorCalculatorFromPrivacyParams(privacyParamsPath string) (*ErrorCalculator, error) {
+ paramsCalculator, err := NewPrivacyEncodingParamsCalculator(privacyParamsPath)
+ if err != nil {
+ return nil, err
+ }
+ errorCalculator := NewErrorCalculator(*paramsCalculator)
+ if err != nil {
+ return nil, err
+ }
+ return errorCalculator, nil
+}
+
// Given a |metric|, |report|, and |params|, estimates the report row error.
//
// TODO(jaredweinstein): implement estimates for other report types.
@@ -31,8 +45,8 @@
return -1, err
}
- populationConstant := e.calc.Constants.population
- privacyEncodingParams, err := e.calc.GetPrivacyEncodingParams(epsilon, populationConstant, sparsity)
+ populationConstant := e.ParamsCalc.Constants.population
+ privacyEncodingParams, err := e.ParamsCalc.GetPrivacyEncodingParams(epsilon, populationConstant, sparsity)
if err != nil {
return -1, err
}
diff --git a/src/bin/config_parser/src/source_generator/json.go b/src/bin/config_parser/src/source_generator/json.go
index 3f974c6..06bb4ed 100644
--- a/src/bin/config_parser/src/source_generator/json.go
+++ b/src/bin/config_parser/src/source_generator/json.go
@@ -8,8 +8,13 @@
import (
"config"
"encoding/json"
+ "privacy"
)
+type jsonOutputter struct {
+ errorCalculator *privacy.ErrorCalculator
+}
+
// JSON export structure
// go tags set the field name when exported to JSON data.
type jsonRegistry struct {
@@ -48,25 +53,66 @@
Reports []jsonReport `json:"reports"`
}
+type jsonError map[string]jsonErrorEstimate
+
type jsonReport struct {
- Name string `json:"name"`
- Id uint32 `json:"id"`
- ReportType string `json:"report_type"`
- ReportTypeId int32 `json:"report_type_id"`
- LocalPrivacyNoiseLevel string `json:"local_privacy_noise_level"`
- PrivacyLevel string `json:"privacy_level"`
- CandidateFile string `json:"candidate_file"`
- CandidateList []string `json:"candidate_list"`
- SystemProfileField []string `json:"system_profile_field"`
- AggregationType string `json:"aggregation_type"`
- WindowSize []string `json:"window_size"`
- LocalAggregationPeriod int32 `json:"local_aggregation_period"`
- LocalAggregationProcedure string `json:"local_aggregation_procedure"`
- LocalAggregationProcedurePercentileN uint32 `json:"local_aggregation_procedure_percentile_n"`
+ Name string `json:"name"`
+ Id uint32 `json:"id"`
+ ReportType string `json:"report_type"`
+ ReportTypeId int32 `json:"report_type_id"`
+ LocalPrivacyNoiseLevel string `json:"local_privacy_noise_level"`
+ PrivacyLevel string `json:"privacy_level"`
+ ErrorEstimates jsonError `json:"error_estimates"`
+ CandidateFile string `json:"candidate_file"`
+ CandidateList []string `json:"candidate_list"`
+ SystemProfileField []string `json:"system_profile_field"`
+ AggregationType string `json:"aggregation_type"`
+ WindowSize []string `json:"window_size"`
+ LocalAggregationPeriod int32 `json:"local_aggregation_period"`
+ LocalAggregationProcedure string `json:"local_aggregation_procedure"`
+ LocalAggregationProcedurePercentileN uint32 `json:"local_aggregation_procedure_percentile_n"`
+}
+
+type jsonErrorEstimate struct {
+ Population uint32 `json:"population"`
+ Epsilon float64 `json:"epsilon"`
+ Estimate float64 `json:"estimate"`
+}
+
+func (jo *jsonOutputter) makeErrorEstimates(report *config.ReportDefinition, metric *config.MetricDefinition) (estimate jsonError) {
+ if jo.errorCalculator == nil {
+ // Error calculator not provided; error estimation skipped.
+ return nil
+ }
+
+ population := report.GetReportingThreshold()
+ if population == 0 {
+ population = 100000
+ }
+
+ var estimates = jsonError{}
+ for l, _ := range config.ReportDefinition_PrivacyLevel_name {
+ level := config.ReportDefinition_PrivacyLevel(l)
+ epsilon := jo.errorCalculator.ParamsCalc.Constants.EpsilonForPrivacyLevel[level]
+ errorValue, err := jo.errorCalculator.Estimate(metric, report, epsilon, uint64(population))
+ if err == nil {
+ estimates[level.String()] = jsonErrorEstimate{
+ Population: population,
+ Epsilon: epsilon,
+ Estimate: errorValue,
+ }
+ }
+ }
+ if len(estimates) == 0 {
+ // Most likely, error estimation is not supported for this specific
+ // report/metric configuration.
+ return nil
+ }
+ return estimates
}
// JSON struct constructors
-func makeJSONReport(report *config.ReportDefinition) jsonReport {
+func (jo *jsonOutputter) makeJSONReport(report *config.ReportDefinition, metric *config.MetricDefinition) jsonReport {
if report == nil {
return jsonReport{}
}
@@ -79,9 +125,10 @@
var windowSize []string
for _, ws := range report.GetWindowSize() {
windowSize = append(windowSize, ws.String())
-
}
+ estimate := jo.makeErrorEstimates(report, metric)
+
return jsonReport{
Name: report.GetReportName(),
Id: report.GetId(),
@@ -89,6 +136,7 @@
ReportTypeId: int32(report.GetReportType()),
LocalPrivacyNoiseLevel: report.GetLocalPrivacyNoiseLevel().String(),
PrivacyLevel: report.GetPrivacyLevel().String(),
+ ErrorEstimates: estimate,
CandidateFile: report.GetCandidateFile(),
CandidateList: report.GetCandidateList(),
SystemProfileField: systemProfileField,
@@ -100,7 +148,7 @@
}
}
-func makeJSONMetric(metric *config.MetricDefinition) jsonMetric {
+func (jo *jsonOutputter) makeJSONMetric(metric *config.MetricDefinition) jsonMetric {
if metric == nil {
return jsonMetric{}
}
@@ -113,7 +161,7 @@
}
var reports []jsonReport
for _, r := range metric.GetReports() {
- reports = append(reports, makeJSONReport(r))
+ reports = append(reports, jo.makeJSONReport(r, metric))
}
var dimensions []string
@@ -148,14 +196,14 @@
}
}
-func makeJSONProject(project *config.ProjectConfig) jsonProject {
+func (jo *jsonOutputter) makeJSONProject(project *config.ProjectConfig) jsonProject {
if project == nil {
return jsonProject{}
}
var metrics []jsonMetric
for _, m := range project.GetMetrics() {
- metrics = append(metrics, makeJSONMetric(m))
+ metrics = append(metrics, jo.makeJSONMetric(m))
}
return jsonProject{
@@ -166,14 +214,14 @@
}
}
-func makeJSONCustomer(customer *config.CustomerConfig) jsonCustomer {
+func (jo *jsonOutputter) makeJSONCustomer(customer *config.CustomerConfig) jsonCustomer {
if customer == nil {
return jsonCustomer{}
}
var projects []jsonProject
for _, p := range customer.GetProjects() {
- projects = append(projects, makeJSONProject(p))
+ projects = append(projects, jo.makeJSONProject(p))
}
return jsonCustomer{
@@ -183,14 +231,14 @@
}
}
-func makeJSONRegistry(registry *config.CobaltRegistry) jsonRegistry {
+func (jo *jsonOutputter) makeJSONRegistry(registry *config.CobaltRegistry) jsonRegistry {
if registry == nil {
return jsonRegistry{}
}
var customers []jsonCustomer
for _, c := range registry.GetCustomers() {
- customers = append(customers, makeJSONCustomer(c))
+ customers = append(customers, jo.makeJSONCustomer(c))
}
return jsonRegistry{
@@ -199,8 +247,8 @@
}
// Outputs the registry contents in JSON
-func JSONOutput(_, filtered *config.CobaltRegistry) (outputBytes []byte, err error) {
- jsonRegistry := makeJSONRegistry(filtered)
+func (jo *jsonOutputter) JSONOutput(_, filtered *config.CobaltRegistry) (outputBytes []byte, err error) {
+ jsonRegistry := jo.makeJSONRegistry(filtered)
prefix := ""
indent := " "
@@ -211,3 +259,19 @@
return jsonBytes, nil
}
+
+// TODO: (jaredweinstein) Remove this deprecated method once call sites are switched.
+func JSONOutput(_, filtered *config.CobaltRegistry) (outputBytes []byte, err error) {
+ // Invalid privacy param path only prevents error estimate struct from being
+ // properly populated.
+ jo := jsonOutputter{nil}
+ return jo.JSONOutput(nil, filtered)
+}
+
+// Returns an output formatter for JSON
+//
+// privacyParamsPath is the string path of the privacy params file to be used for error estimation.
+func JSONOutputFactory(errorCalculator *privacy.ErrorCalculator) OutputFormatter {
+ jo := jsonOutputter{errorCalculator}
+ return jo.JSONOutput
+}
diff --git a/src/bin/config_parser/src/source_generator/json_test.go b/src/bin/config_parser/src/source_generator/json_test.go
index b700ea5..1d3eb63 100644
--- a/src/bin/config_parser/src/source_generator/json_test.go
+++ b/src/bin/config_parser/src/source_generator/json_test.go
@@ -8,34 +8,96 @@
import (
"config"
+ "privacy"
"reflect"
"testing"
)
+var testParamRecords = [][]string{
+ {"1.0", "10000", "1", "0.0024182694032788277", "9"},
+ {"1.0", "20000", "1", "0.0013450710102915764", "10"},
+ {"1.0", "10000", "2", "0.004249398596584797", "7"},
+ {"1.0", "20000", "2", "0.002368534915149212", "9"},
+ {"1.0", "10000", "10", "0.019537480548024178", "4"},
+ {"1.0", "20000", "10", "0.011127800680696964", "5"},
+ {"5.0", "10000", "1", "0.000906200148165226", "12"},
+ {"5.0", "20000", "1", "0.000491277314722538", "15"},
+ {"5.0", "10000", "2", "0.0009743086993694305", "12"},
+ {"5.0", "20000", "2", "0.0005256505683064461", "14"},
+ {"5.0", "10000", "10", "0.0014028092846274376", "10"},
+ {"5.0", "20000", "10", "0.0007524723187088966", "13"},
+}
+
+func constructTestJsonOutputter(t *testing.T) jsonOutputter {
+ paramsCalc, err := privacy.NewPrivacyEncodingParamsCalculatorForTesting(testParamRecords)
+ if err != nil {
+ t.Errorf("Failed to create error calculator.")
+ }
+ errorCalc := privacy.NewErrorCalculator(*paramsCalc)
+ return jsonOutputter{errorCalc}
+}
+
func TestConstructorsHandleNil(t *testing.T) {
- r := makeJSONReport(nil)
+ jo := constructTestJsonOutputter(t)
+ r := jo.makeJSONReport(nil, nil)
if reflect.DeepEqual(r, (jsonReport{})) == false {
t.Errorf("makeJSONReport failed to return empty report got = %#v", r)
}
- m := makeJSONMetric(nil)
+ m := jo.makeJSONMetric(nil)
if reflect.DeepEqual(m, (jsonMetric{})) == false {
t.Errorf("makeJSONMetric failed to return empty metric got = %#v", m)
}
- p := makeJSONProject(nil)
+ p := jo.makeJSONProject(nil)
if reflect.DeepEqual(p, (jsonProject{})) == false {
t.Errorf("makeJSONProject failed to return empty project got = %#v", p)
}
- c := makeJSONCustomer(nil)
+ c := jo.makeJSONCustomer(nil)
if reflect.DeepEqual(c, (jsonCustomer{})) == false {
t.Errorf("makeJSONCustomer failed to return empty customer got = %#v", c)
}
- rg := makeJSONRegistry(nil)
+ rg := jo.makeJSONRegistry(nil)
if reflect.DeepEqual(rg, (jsonRegistry{})) == false {
t.Errorf("makeJSONRegistry failed to return empty registry got = %#v", rg)
}
}
+func TestMakeErrorEstimates(t *testing.T) {
+ jo := constructTestJsonOutputter(t)
+ r := config.ReportDefinition{
+ ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
+ PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
+ LocalAggregationProcedure: config.ReportDefinition_SELECT_FIRST,
+ }
+ m := config.MetricDefinition{
+ MetricType: config.MetricDefinition_OCCURRENCE,
+ }
+
+ want := jsonError{
+ "LOW_PRIVACY": jsonErrorEstimate{
+ Population: 100000,
+ Epsilon: 10,
+ Estimate: 9.532416856027137,
+ },
+ "MEDIUM_PRIVACY": jsonErrorEstimate{
+ Population: 100000,
+ Epsilon: 5,
+ Estimate: 9.532416856027137,
+ },
+ "HIGH_PRIVACY": jsonErrorEstimate{
+ Population: 100000,
+ Epsilon: 1,
+ Estimate: 15.607457540599944,
+ },
+ }
+
+ got := jo.makeErrorEstimates(&r, &m)
+ if reflect.DeepEqual(want, got) == false {
+ t.Errorf("makeJSONReport(%#v)\n\n GOT: %#v\nWANT: %#v", r, got, want)
+ }
+}
+
func TestMakeJSONReport(t *testing.T) {
+ jo := constructTestJsonOutputter(t)
name := "test_name"
id := uint32(123456789)
candidate_file := "test_file_name"
@@ -58,6 +120,11 @@
LocalAggregationProcedure: config.ReportDefinition_SUM_PROCEDURE,
LocalAggregationProcedurePercentileN: aggregation_percentile,
}
+ m := config.MetricDefinition{
+ MetricName: name,
+ Id: id,
+ MetricType: config.MetricDefinition_EVENT_OCCURRED,
+ }
want := jsonReport{
Name: name,
@@ -66,6 +133,7 @@
ReportTypeId: int32(config.ReportDefinition_SIMPLE_OCCURRENCE_COUNT),
LocalPrivacyNoiseLevel: "NOISE_LEVEL_UNSET",
PrivacyLevel: "LOW_PRIVACY",
+ ErrorEstimates: nil,
CandidateFile: candidate_file,
CandidateList: candidate_list,
SystemProfileField: []string{"OS", "ARCH"},
@@ -76,13 +144,14 @@
LocalAggregationProcedurePercentileN: aggregation_percentile,
}
- got := makeJSONReport(&r)
+ got := jo.makeJSONReport(&r, &m)
if reflect.DeepEqual(want, got) == false {
t.Errorf("makeJSONReport(%#v)\n\n GOT: %#v\nWANT: %#v", r, got, want)
}
}
func TestMakeJSONMetric(t *testing.T) {
+ jo := constructTestJsonOutputter(t)
name := "test_name"
id := uint32(123456789)
owners := []string{"owners", "owner2"}
@@ -145,13 +214,14 @@
Reports: emptyReports,
}
- got := makeJSONMetric(&m)
+ got := jo.makeJSONMetric(&m)
if reflect.DeepEqual(want, got) == false {
t.Errorf("makeJSONMetric(%#v)\n\n GOT: %#v\nWANT: %#v", m, got, want)
}
}
func TestMakeJSONMetricEmptyMetadata(t *testing.T) {
+ jo := constructTestJsonOutputter(t)
name := "test_name"
reports := []*config.ReportDefinition{nil, nil}
m := config.MetricDefinition{
@@ -168,13 +238,14 @@
Reports: emptyReports,
}
- got := makeJSONMetric(&m)
+ got := jo.makeJSONMetric(&m)
if reflect.DeepEqual(want, got) == false {
t.Errorf("makeJSONMetric(%#v)\n\n GOT: %#v\nWANT: %#v", m, got, want)
}
}
func TestMakeJSONProject(t *testing.T) {
+ jo := constructTestJsonOutputter(t)
name := "test_name"
id := uint32(123456789)
metrics := []*config.MetricDefinition{nil, nil}
@@ -194,13 +265,14 @@
Metrics: emptyMetrics,
}
- got := makeJSONProject(&p)
+ got := jo.makeJSONProject(&p)
if reflect.DeepEqual(want, got) == false {
t.Errorf("makeJSONProject(%#v)\n\n GOT: %#v\nWANT: %#v", p, got, want)
}
}
func TestMakeJSONCustomer(t *testing.T) {
+ jo := constructTestJsonOutputter(t)
name := "test_name"
id := uint32(123456789)
projects := []*config.ProjectConfig{nil, nil}
@@ -217,24 +289,25 @@
Projects: emptyProjects,
}
- got := makeJSONCustomer(&c)
+ got := jo.makeJSONCustomer(&c)
if reflect.DeepEqual(want, got) == false {
t.Errorf("makeJSONCustomer(%#v)\n\n GOT: %#v\nWANT: %#v", c, got, want)
}
}
func TestMakeJSONRegistry(t *testing.T) {
+ jo := constructTestJsonOutputter(t)
customers := []*config.CustomerConfig{nil, nil}
r := config.CobaltRegistry{
Customers: customers,
}
- emptyProjects := []jsonCustomer{jsonCustomer{}, jsonCustomer{}}
+ emptyCustomers := []jsonCustomer{jsonCustomer{}, jsonCustomer{}}
want := jsonRegistry{
- Customers: emptyProjects,
+ Customers: emptyCustomers,
}
- got := makeJSONRegistry(&r)
+ got := jo.makeJSONRegistry(&r)
if reflect.DeepEqual(want, got) == false {
t.Errorf("makeJSONRegistry(%#v)\n\n GOT: %#v\nWANT: %#v", r, got, want)
}
diff --git a/src/bin/config_parser/src/source_generator/source_generator_test.go b/src/bin/config_parser/src/source_generator/source_generator_test.go
index 614f62f..6006ab3 100644
--- a/src/bin/config_parser/src/source_generator/source_generator_test.go
+++ b/src/bin/config_parser/src/source_generator/source_generator_test.go
@@ -197,7 +197,7 @@
{"GoldenForTesting.java", JavaOutputFactory("config", []string{}, []string{"testing"}, "golden_for_testing"), false},
// JSONOutput() ignores hideOnClients so we only test once
- {"golden.cb.json", JSONOutput, true},
+ {"golden.cb.json", JSONOutputFactory(nil), true},
}
func TestPrintConfig(t *testing.T) {
diff --git a/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json b/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json
index 0cf15d6..3e54111 100644
--- a/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json
+++ b/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json
@@ -33,6 +33,7 @@
"report_type_id": 9999,
"local_privacy_noise_level": "NOISE_LEVEL_UNSET",
"privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+ "error_estimates": null,
"candidate_file": "",
"candidate_list": null,
"system_profile_field": null,
@@ -49,6 +50,7 @@
"report_type_id": 3,
"local_privacy_noise_level": "NOISE_LEVEL_UNSET",
"privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+ "error_estimates": null,
"candidate_file": "",
"candidate_list": null,
"system_profile_field": null,
@@ -88,6 +90,7 @@
"report_type_id": 1,
"local_privacy_noise_level": "NOISE_LEVEL_UNSET",
"privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+ "error_estimates": null,
"candidate_file": "",
"candidate_list": null,
"system_profile_field": null,
@@ -131,6 +134,7 @@
"report_type_id": 1,
"local_privacy_noise_level": "NOISE_LEVEL_UNSET",
"privacy_level": "LOW_PRIVACY",
+ "error_estimates": null,
"candidate_file": "",
"candidate_list": null,
"system_profile_field": null,
@@ -184,6 +188,7 @@
"report_type_id": 0,
"local_privacy_noise_level": "NOISE_LEVEL_UNSET",
"privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+ "error_estimates": null,
"candidate_file": "",
"candidate_list": null,
"system_profile_field": null,
diff --git a/src/bin/config_parser/src/source_generator/source_outputter.go b/src/bin/config_parser/src/source_generator/source_outputter.go
index 2ab9b8f..003add5 100644
--- a/src/bin/config_parser/src/source_generator/source_outputter.go
+++ b/src/bin/config_parser/src/source_generator/source_outputter.go
@@ -10,6 +10,7 @@
"bytes"
"config"
"fmt"
+ "privacy"
"reflect"
"sort"
"strconv"
@@ -442,7 +443,7 @@
}
}
-func getOutputFormatter(format, namespace, goPackageName, varName string, features []string, outFilename string) (OutputFormatter, error) {
+func getOutputFormatter(format, namespace, goPackageName, varName string, features []string, outFilename string, errorCalculator *privacy.ErrorCalculator) (OutputFormatter, error) {
namespaceList := []string{}
if namespace != "" {
namespaceList = strings.Split(namespace, ".")
@@ -464,7 +465,7 @@
case "java":
return JavaOutputFactory(varName, namespaceList, features, outFilename), nil
case "json":
- return JSONOutput, nil
+ return JSONOutputFactory(errorCalculator), nil
case "rust":
return RustOutputFactory(varName, namespaceList, features), nil
default:
diff --git a/src/bin/config_parser/src/source_generator/source_outputter_test.go b/src/bin/config_parser/src/source_generator/source_outputter_test.go
index f8df45d..b6d04ed 100644
--- a/src/bin/config_parser/src/source_generator/source_outputter_test.go
+++ b/src/bin/config_parser/src/source_generator/source_outputter_test.go
@@ -4,13 +4,17 @@
package source_generator
-import "testing"
+import (
+ "privacy"
+ "testing"
+)
func TestGetOutputFormatter(t *testing.T) {
formats := []string{"bin", "b64", "cpp", "dart", "rust", "go", "java", "json"}
+ errorCalculator := privacy.ErrorCalculator{privacy.PrivacyEncodingParamsCalculator{}}
for _, format := range formats {
- outputFormatter, err := getOutputFormatter(format, "ns", "package", "varName", []string{}, "")
+ outputFormatter, err := getOutputFormatter(format, "ns", "package", "varName", []string{}, "", &errorCalculator)
if outputFormatter == nil {
t.Errorf("Unexpected nil output formatter for format %v", format)
}
@@ -19,7 +23,7 @@
}
}
- outputFormatter, err := getOutputFormatter("blah", "ns", "package", "varName", []string{}, "")
+ outputFormatter, err := getOutputFormatter("invalid_format", "ns", "package", "varName", []string{}, "", &errorCalculator)
if outputFormatter != nil {
t.Errorf("Unexpectedly got an output formatter.")
}
diff --git a/src/bin/config_parser/src/source_generator/writer.go b/src/bin/config_parser/src/source_generator/writer.go
index e485458..fa7c43e 100644
--- a/src/bin/config_parser/src/source_generator/writer.go
+++ b/src/bin/config_parser/src/source_generator/writer.go
@@ -11,22 +11,24 @@
"flag"
"fmt"
"os"
+ "privacy"
"strings"
)
var (
- addFileSuffix = flag.Bool("add_file_suffix", false, "Append the out_format to the out_file, even if there is only one out_format specified")
- outFile = flag.String("output_file", "", "File to which the serialized config should be written. Defaults to stdout. When multiple output formats are specified, it will append the format to the filename")
- outFilename = flag.String("out_filename", "", "The base name to use for writing files. Should not be used with output_file.")
- outDir = flag.String("out_dir", "", "The directory into which files should be written.")
- outFormat = flag.String("out_format", "bin", "Specifies the output formats (separated by ' '). Supports 'bin' (serialized proto), 'go' (a golang package), 'b64' (serialized proto to base 64), 'cpp' (a C++ file containing a variable with a base64-encoded serialized proto) 'dart' (a Dart library), 'json' (a JSON object), and 'rust' (a rust crate)")
- features = flag.String("features", "", "A comma separated list of source generator features to enable.")
- namespace = flag.String("namespace", "", "When using the 'cpp', 'rust', or 'go' output format, this will specify the period-separated namespace within which the config variable must be placed (this will be transformed into an underscore-separated package name for go).")
- goPackageName = flag.String("go_package", "", "When using the 'go' output format, this will specify the package for generated code.")
- dartOutDir = flag.String("dart_out_dir", "", "The directory to write dart files to (if different from out_dir)")
- varName = flag.String("var_name", "config", "When using the 'cpp' or 'dart' output format, this will specify the variable name to be used in the output.")
- checkOnly = flag.Bool("check_only", false, "Only check that the configuration is valid.")
- allowEmptyOutput = flag.Bool("allow_empty_output", false, "Relax the requirement that the cobalt registry output not be empty.")
+ addFileSuffix = flag.Bool("add_file_suffix", false, "Append the out_format to the out_file, even if there is only one out_format specified")
+ outFile = flag.String("output_file", "", "File to which the serialized config should be written. Defaults to stdout. When multiple output formats are specified, it will append the format to the filename")
+ outFilename = flag.String("out_filename", "", "The base name to use for writing files. Should not be used with output_file.")
+ outDir = flag.String("out_dir", "", "The directory into which files should be written.")
+ outFormat = flag.String("out_format", "bin", "Specifies the output formats (separated by ' '). Supports 'bin' (serialized proto), 'go' (a golang package), 'b64' (serialized proto to base 64), 'cpp' (a C++ file containing a variable with a base64-encoded serialized proto) 'dart' (a Dart library), 'json' (a JSON object), and 'rust' (a rust crate)")
+ features = flag.String("features", "", "A comma separated list of source generator features to enable.")
+ namespace = flag.String("namespace", "", "When using the 'cpp', 'rust', or 'go' output format, this will specify the period-separated namespace within which the config variable must be placed (this will be transformed into an underscore-separated package name for go).")
+ goPackageName = flag.String("go_package", "", "When using the 'go' output format, this will specify the package for generated code.")
+ dartOutDir = flag.String("dart_out_dir", "", "The directory to write dart files to (if different from out_dir)")
+ varName = flag.String("var_name", "config", "When using the 'cpp' or 'dart' output format, this will specify the variable name to be used in the output.")
+ privacyParamsPath = flag.String("privacy_params_path", "", "For output formats that require an error estimate, this specifies the path to a file containing required privacy parameters.")
+ checkOnly = flag.Bool("check_only", false, "Only check that the configuration is valid.")
+ allowEmptyOutput = flag.Bool("allow_empty_output", false, "Relax the requirement that the cobalt registry output not be empty.")
)
// checkFlags verifies that the specified flags are compatible with each other.
@@ -83,10 +85,20 @@
if err := checkFlags(); err != nil {
return err
}
+
+ var errorCalc *privacy.ErrorCalculator
+ if *privacyParamsPath != "" {
+ var err error
+ errorCalc, err = privacy.NewErrorCalculatorFromPrivacyParams(*privacyParamsPath)
+ if err != nil {
+ return err
+ }
+ }
+
generateFilename := filenameGeneratorFromFlags()
features := strings.Split(*features, ",")
for _, format := range parseOutFormatList(*outFormat) {
- outputFormatter, err := getOutputFormatter(format, *namespace, *goPackageName, *varName, features, *outFilename)
+ outputFormatter, err := getOutputFormatter(format, *namespace, *goPackageName, *varName, features, *outFilename, errorCalc)
if err != nil {
return err
}