Skip to content

Commit

Permalink
SampleGen: default calling forms (googleapis#2713)
Browse files Browse the repository at this point in the history
When:
```yaml
- samples:
    standalone:
    - region_tag: some_tag
   # unspecified calling forms
      value_sets: a_value_set, b_value_set
```
Default calling forms are used.

Also use region tags as file names when there is only one sample that has this region tag.
  • Loading branch information
yihanzhen authored and busunkim96 committed Nov 7, 2019
1 parent 5addb73 commit 0db3a51
Show file tree
Hide file tree
Showing 26 changed files with 1,418 additions and 510 deletions.
63 changes: 63 additions & 0 deletions src/main/java/com/google/api/codegen/config/SampleConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Copyright 2019 Google LLC
*
* 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
*
* https://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 com.google.api.codegen.config;

import com.google.api.codegen.SampleValueSet;
import com.google.api.codegen.viewmodel.CallingForm;
import com.google.auto.value.AutoValue;

/** SampleConfig represents configurations of a sample. */
@AutoValue
public abstract class SampleConfig {

public abstract String regionTag();

public abstract CallingForm callingForm();

public abstract SampleValueSet valueSet();

public abstract SampleSpec.SampleType type();

public static SampleConfig create(
String regionTag,
CallingForm callingForm,
SampleValueSet valueSet,
SampleSpec.SampleType type) {
return newBuilder()
.regionTag(regionTag)
.callingForm(callingForm)
.valueSet(valueSet)
.type(type)
.build();
}

public static Builder newBuilder() {
return new AutoValue_SampleConfig.Builder();
}

@AutoValue.Builder
public abstract static class Builder {

public abstract Builder regionTag(String val);

public abstract Builder callingForm(CallingForm val);

public abstract Builder valueSet(SampleValueSet val);

public abstract Builder type(SampleSpec.SampleType val);

public abstract SampleConfig build();
}
}
94 changes: 66 additions & 28 deletions src/main/java/com/google/api/codegen/config/SampleSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import com.google.api.codegen.viewmodel.CallingForm;
import com.google.auto.value.AutoValue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -112,40 +114,76 @@ private static boolean expressionMatchesId(String expression, String id) {
}

/**
* Returns the SampleValueSets that were specified for this methodForm and sampleType.
* Matches all the IDs within `targets` that match one or more elements of `expressions`. The IDs
* are extracted from elements of targets via `targetToId`."
*/
private static <T> List<T> expressionsMatchIds(
List<String> expressions, List<T> targets, Function<T, String> targetToId) {
return targets
.stream()
.filter(
t ->
expressions.stream().anyMatch(exp -> expressionMatchesId(exp, targetToId.apply(t))))
.collect(Collectors.toList());
}

/**
* Returns all the valid combinations of calling forms of value sets.
*
* @param methodForm The calling form for which value sets are requested
* @param sampleType The sample type for which value sets are requested
* @return A set of SampleValueSets for methodForm andSampleType
* <p>We match calling forms specified by users with `allValidCallingForms`. If users did not
* specify any calling forms, we match `defaultCallingForm` with `allValidCallingForms`. If we
* found no matching calling forms, an empty list will be returned. Note this implies that
* `defaultCallingForm` can match none of the calling forms in `allValidCallingForms`. For
* example, the `defaultCallingForm` for a C# unary call is `Request`, but it's not a valid
* calling form when generating asynchronous samples using the generated asynchonous public
* method. In this case we will not generate samples for this method.
*
* <p>We match value sets specified by users with `this.values`. If we find no matching value
* sets, we use the default one. For incode samples, we always use the default value set derived
* from sample_code_init_fields for backward compatibility.
*
* <p>We should probably disable default value sets after incode samples stop using
* `sample_code_init_fields`.
*/
public List<ValueSetAndTags> getMatchingValueSets(CallingForm methodForm, SampleType sampleType) {
String methodFormString = Name.anyCamel(methodForm.toString()).toLowerUnderscore();

// Get the `SampleTypeConfigs` configured for this `methodForm`.
List<SampleTypeConfiguration> matchingSamples =
getConfigFor(sampleType)
.stream()
.filter(
sampleConfig ->
sampleConfig
.getCallingFormsList()
.stream()
.anyMatch(expression -> expressionMatchesId(expression, methodFormString)))
.collect(Collectors.toList());

// Construct a `ValueSetAndTags` for each sample specified in each element of `matchingSamples`.
List<ValueSetAndTags> result = new ArrayList<>();
for (SampleValueSet vset : valueSets) {
for (SampleTypeConfiguration sample : matchingSamples) {
for (String valueSetExpression : sample.getValueSetsList()) {
if (expressionMatchesId(valueSetExpression, vset.getId())) {
result.add(
ValueSetAndTags.newBuilder().values(vset).regionTag(sample.getRegionTag()).build());
public List<SampleConfig> getSampleConfigs(
List<CallingForm> allValidCallingForms,
CallingForm defaultCallingForm,
SampleValueSet defaultValueSet,
SampleType type) {
List<SampleConfig> sampleConfigs = new ArrayList<>();
if (type == SampleType.EXPLORER) {
throw new UnsupportedOperationException("API Explorer samples unimplemented yet.");
} else if (type == SampleType.IN_CODE) {
for (CallingForm form : allValidCallingForms) {
sampleConfigs.add(SampleConfig.create("", form, defaultValueSet, type));
}
} else {
for (SampleTypeConfiguration config : getConfigFor(type)) {
List<CallingForm> matchingCallingForms = null;
List<SampleValueSet> matchingValueSets = null;

List<String> callingFormNames =
config.getCallingFormsList().isEmpty()
? Collections.singletonList(
Name.anyCamel(defaultCallingForm.toString()).toLowerUnderscore())
: config.getCallingFormsList();
matchingCallingForms =
expressionsMatchIds(
callingFormNames, allValidCallingForms, CallingForm::toLowerUnderscore);

matchingValueSets =
expressionsMatchIds(config.getValueSetsList(), valueSets, v -> v.getId());

for (CallingForm form : matchingCallingForms) {
for (SampleValueSet matchingValueSet : matchingValueSets) {
sampleConfigs.add(
SampleConfig.create(config.getRegionTag(), form, matchingValueSet, type));
}
}
}
}
return result;

return sampleConfigs;
}

/** Returns the single {@code SampleTypeConfiguration} for the specified {@code sampleType}. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,78 @@
*/
package com.google.api.codegen.transformer;

import com.google.api.codegen.viewmodel.MethodSampleView;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* {@code SampleFileRegistry} is used to verify that the samples we generate with different
* parameters have different paths, so they don't clobber each other.
*
* <p>If a sample has a unique region tag, the file name of the sample will be its region tag in the
* language-idiomatic case, followed by an appropriate extension.
*
* <p>If the region tag of a sample is not unique, the file name of the sample will be constructed
* by concatinating `method_name`, `calling_form` and `value_set_id`. The file name will be in the
* language-idiomatic case and followed by an appropriate extension as well.
*/
public class SampleFileRegistry {

private final Map<String, SampleInfo> files = new HashMap<>();
private final Map<String, Integer> regionTagCount = new HashMap<>();
private final SurfaceNamer namer;

public SampleFileRegistry(SurfaceNamer namer, List<MethodSampleView> allSamples) {
this.namer = namer;
for (MethodSampleView sample : allSamples) {
regionTagCount.put(
sample.regionTag(), regionTagCount.getOrDefault(sample.regionTag(), 0) + 1);
}
}

public String getSampleClassName(MethodSampleView sample, String method) {
String regionTag = sample.regionTag();
Preconditions.checkState(
regionTagCount.get(regionTag) != null && regionTagCount.get(regionTag) > 0,
"Sample not registered.");
if (regionTagCount.get(regionTag) == 1) {
return namer.getApiSampleClassName(regionTag);
} else {
String callingForm = sample.callingForm().toLowerCamel();
String valueSet = sample.valueSet().id();
return namer.getApiSampleClassName(
method, sample.callingForm().toLowerUnderscore(), sample.valueSet().id());
}
}

public String getSampleFileName(MethodSampleView sample, String method) {
String regionTag = sample.regionTag();
String callingForm = sample.callingForm().toLowerCamel();
String valueSet = sample.valueSet().id();
Preconditions.checkState(
regionTagCount.get(regionTag) != null && regionTagCount.get(regionTag) > 0,
"Sample not registered.");
String fileName;
if (regionTagCount.get(regionTag) == 1) {
fileName = namer.getApiSampleFileName(regionTag);
} else {
fileName =
namer.getApiSampleFileName(
method, sample.callingForm().toLowerUnderscore(), sample.valueSet().id());
}
addFile(fileName, method, callingForm, valueSet, regionTag);
return fileName;
}

/**
* Adds a file with the given parameters to the registry. If a file with the given {@code path}
* previously existed in the registry and any of the parameters don't match, throws an exception
* describing the conflict.
*/
public void addFile(
private void addFile(
String path, String method, String callingForm, String valueSet, String regionTag) {
SampleInfo current =
SampleInfo.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import com.google.api.codegen.config.MethodConfig;
import com.google.api.codegen.config.MethodContext;
import com.google.api.codegen.config.OutputContext;
import com.google.api.codegen.config.SampleConfig;
import com.google.api.codegen.config.SampleParameterConfig;
import com.google.api.codegen.config.SampleSpec.SampleType;
import com.google.api.codegen.config.SampleSpec.ValueSetAndTags;
import com.google.api.codegen.metacode.InitCodeContext;
import com.google.api.codegen.metacode.InitCodeContext.InitCodeOutputType;
import com.google.api.codegen.util.Name;
Expand Down Expand Up @@ -202,42 +202,34 @@ public List<MethodSampleView> generateSamples(
InitCodeOutputType initCodeOutputType,
List<CallingForm> callingForms) {

CallingForm defaultCallingForm = methodContext.getNamer().getDefaultCallingForm(methodContext);
List<MethodSampleView> methodSampleViews = new ArrayList<>();
MethodConfig methodConfig = methodContext.getMethodConfig();
ImmutableList<ValueSetAndTags> defaultValueSets = defaultValueSets(methodConfig);
for (CallingForm form : callingForms) {
List<ValueSetAndTags> matchingValueSets =
methodConfig.getSampleSpec().getMatchingValueSets(form, sampleType());

if (sampleType() == SampleType.IN_CODE
|| !methodConfig.getSampleSpec().isConfigured()
|| matchingValueSets.isEmpty()) {
matchingValueSets = defaultValueSets;
}

for (ValueSetAndTags setAndTag : matchingValueSets) {
// Don't overwrite the initContext in outer scope.
InitCodeContext thisContext = initContext;
SampleValueSet valueSet = setAndTag.values();
if (thisContext == null) {
thisContext =
createInitCodeContext(methodContext, fieldConfigs, initCodeOutputType, valueSet);
}
methodSampleViews.add(generateSample(setAndTag, form, methodContext, thisContext));
SampleValueSet defaultValueSet = defaultValueSet(methodConfig);

for (SampleConfig sampleConfig :
methodConfig
.getSampleSpec()
.getSampleConfigs(callingForms, defaultCallingForm, defaultValueSet, sampleType())) {
InitCodeContext thisContext = initContext; // Do not override outer initContext
if (thisContext == null) {
thisContext =
createInitCodeContext(
methodContext, fieldConfigs, initCodeOutputType, sampleConfig.valueSet());
}
methodSampleViews.add(generateSample(sampleConfig, methodContext, thisContext));
}
return methodSampleViews;
}

private MethodSampleView generateSample(
ValueSetAndTags setAndTag,
CallingForm form,
MethodContext methodContext,
InitCodeContext initCodeContext) {
SampleConfig config, MethodContext methodContext, InitCodeContext initCodeContext) {
methodContext = methodContext.cloneWithEmptyTypeTable();
InitCodeView initCodeView =
initCodeTransformer().generateInitCode(methodContext, initCodeContext);
SampleValueSet valueSet = setAndTag.values();
SampleValueSet valueSet = config.valueSet();
CallingForm form = config.callingForm();
String regionTag = config.regionTag();
List<OutputSpec> outputs = valueSet.getOnSuccessList();
if (outputs.isEmpty()) {
outputs = OutputTransformer.defaultOutputSpecs(methodContext);
Expand Down Expand Up @@ -280,10 +272,7 @@ private MethodSampleView generateSample(
.sampleImports(sampleImportSectionView)
.regionTag(
regionTagFromSpec(
setAndTag.regionTag(),
methodContext.getMethodModel().getSimpleName(),
form,
valueSet.getId()))
regionTag, methodContext.getMethodModel().getSimpleName(), form, valueSet.getId()))
.sampleFunctionName(
methodContext.getNamer().getSampleFunctionName(methodContext.getMethodModel()))
.sampleFunctionDoc(sampleFunctionDocView)
Expand Down Expand Up @@ -323,28 +312,22 @@ private InitCodeContext createInitCodeContext(
.build();
}

private ImmutableList<ValueSetAndTags> defaultValueSets(MethodConfig methodConfig) {
private SampleValueSet defaultValueSet(MethodConfig methodConfig) {
// For backwards compatibility in the configs, we need to use sample_code_init_fields instead
// to generate the samples in various scenarios. Once all the configs have been migrated to
// use the SampleSpec, we can delete the code below as well as sample_code_init_fields.
String defaultId =
(sampleType() == SampleType.IN_CODE) ? "sample_code_init_field" : INIT_CODE_SHIM;
ImmutableList<ValueSetAndTags> defaultValueSets =
ImmutableList.of(
ValueSetAndTags.newBuilder()
.values(
SampleValueSet.newBuilder()
.setParameters(
SampleParameters.newBuilder()
.addAllDefaults(methodConfig.getSampleCodeInitFields())
.build())
.setId(defaultId)
.setDescription("value set imported from sample_code_init_fields")
.setTitle("Sample Values")
.build())
.regionTag("")
.build());
return defaultValueSets;

return SampleValueSet.newBuilder()
.setParameters(
SampleParameters.newBuilder()
.addAllDefaults(methodConfig.getSampleCodeInitFields())
.build())
.setId(defaultId)
.setDescription("value set imported from sample_code_init_fields")
.setTitle("Sample Values")
.build();
}

private ImmutableMap<String, SampleParameterConfig> sampleParamConfigMapFromValueSet(
Expand Down
Loading

0 comments on commit 0db3a51

Please sign in to comment.