Skip to content

Commit

Permalink
[ENG-1707] Generate Ruby SDK for Carbon SDKs (#589)
Browse files Browse the repository at this point in the history
* "useSecurityKeyNameAsPropertyName" for Ruby

* conditionally embed other auth methods in Ruby SDK

* typo

* fix api key doc generation

* suppor readmeHeader in Ruby SDK

* add table of contents

* update ruby integration test snapshot

* docs(changeset): support generating table of contents for Ruby SDK

* fix

* fix spacing

* use instance version of client

* delete docs/ files + generate snippets in reference section

* regenerate README.md w/ parameters

* fix ruby test

* fix tests

* update

* api_key -> api_key_store

* do not use def_delegate

- make is_sentinel private

* update usage snippet to be more concise

* don't escape characters

* use ubicloud for GHA runner

* improve in-line docs

* regenerate snaptrade integration test snapshot

* fix boolean literals in Ruby docs + use backticks for data types

* add return + endpoint sections in reference

* put conditionals on newlines for api_doc_param_data_type

* override AbstractRubyCodegen.java

* ensure all hyperlinks work

* rerun integration test for snaptrade

* use static pattern in docs

* use static method in docs

* improve TypeScript examples

* remove model docs

* fix docs for file.upload in Ruby

* rerun snaptrade integration test for Ruby

* Revert "use ubicloud for GHA runner"

This reverts commit 7e3058b.

* use ubicloud for integration tests only

* useNewWithHttpInfoReturnType

* change output directories

* try again

* _with_http_info docs

* rerun ruby-snaptrade

* fix body param not being named

* fix parameter name for body

* render data type for any

* fix `any` type not being rendered for TS SDK

* add ruby-carbon

* switch to larj-boy

* wrote tests for asserting on security requirements

* works

* api_key_prefix works

* add snapshot

* add test for default value

* support readmeSnippet in Ruby

* add readmeSnippet to integration test for carbon-ruby

* add tests for getting started snippet

* back to instance

* change scrript to work

* store this for keep-sake

* fix snaptrade test
  • Loading branch information
dphuang2 authored Feb 29, 2024
1 parent 29ba3f9 commit c1ac616
Show file tree
Hide file tree
Showing 992 changed files with 111,947 additions and 7,359 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/konfig-sdk-integration-tests-earthly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

jobs:
check_for_relevant_changes:
runs-on: ubuntu-latest
runs-on: ubicloud
outputs:
shouldRun: ${{ steps.check_changes.outputs.shouldRun }}
steps:
Expand All @@ -30,7 +30,7 @@ jobs:
execute_integration_tests:
needs: check_for_relevant_changes
runs-on: ubuntu-latest
runs-on: ubicloud
if: >
needs.check_for_relevant_changes.outputs.shouldRun == 'true' ||
contains(github.event.head_commit.message, 'run-integration-tests') ||
Expand Down Expand Up @@ -64,5 +64,5 @@ jobs:
run: export EARTHLY_DISABLE_REMOTE_REGISTRY_PROXY=1

- name: Build and run integration tests
run: earthly --auto-skip --ci --sat xlarj-boi --secret AWS_ACCESS_KEY_ID --secret AWS_SECRET_ACCESS_KEY --secret NPM_TOKEN=dummy --secret GITHUB_ACTIONS -P +test
run: earthly --auto-skip --ci --sat larj-boy --secret AWS_ACCESS_KEY_ID --secret AWS_SECRET_ACCESS_KEY --secret NPM_TOKEN=dummy --secret GITHUB_ACTIONS -P +test
working-directory: ./generator/konfig-integration-tests
5 changes: 5 additions & 0 deletions generator/konfig-dash/.changeset/smooth-goats-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'konfig-cli': patch
---

support generating table of contents for Ruby SDK
2 changes: 1 addition & 1 deletion generator/konfig-dash/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ konfig-test-dependencies:
lsof \ # used by konfig test
openssl # used by prisma
RUN gem install bundler
RUN curl -s https://archive.swiftlang.xyz/install.sh | bash
RUN curl -s https://packagecloud.io/install/repositories/swift-arm/release/script.deb.sh | bash
RUN apt install -y swiftlang
RUN swift --version
RUN npm install -g pnpm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export default class Deploy extends Command {
const { files, ...restOfConfig } = rubyGeneratorConfig
const requestJava: GenerateRequestBodyInputType['generators']['ruby'] =
{
files: createTemplateFilesObject(files, 'csharp', configDir),
files: createTemplateFilesObject(files, 'ruby', configDir),
...handleReadmeSnippet({ config: restOfConfig }),
...handleReadmeSupportingDescriptionSnippet({
config: restOfConfig,
Expand Down Expand Up @@ -1034,6 +1034,12 @@ export default class Deploy extends Command {
generatorConfig: body.generators.ruby,
})
CliUx.ux.action.stop()
// insert TOC at beginning of README.md
CliUx.ux.action.start(
'Inserting table of contents into README.md'
)
insertTableOfContents({ outputDirectory })
CliUx.ux.action.stop()
}
}

Expand Down
131 changes: 70 additions & 61 deletions generator/konfig-dash/packages/konfig-lib/src/transformSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,70 +102,18 @@ export const transformSpec = async ({
})

if (generator === 'typescript') {
// When using enum strings for some reason wrapping it inside of an allOf with 1 schema fixes an issue
// where the generated data type in TypeScript's README.md is empty
// before:
// ##### lead_type:<a id="lead_type"></a>
// after:
// ##### lead_type: [`LeadType`](./models/lead-type.ts)<a id="lead_type-leadtypemodelslead-typets"></a>

// find all ref objects that are pointing to "type": "string" with an "enum" property
// and wrap them in an allOf with 1 schema
recurseObject(spec.spec, ({ value: schema, path }) => {
if (schema === null) return
if (typeof schema !== 'object') return

// ensure schema has a $ref property
if (!('$ref' in schema)) return

const resolvedSchema = resolveRef({
refOrObject: schema,
$ref: spec.$ref,
})

if (resolvedSchema['type'] !== 'string') return
if (resolvedSchema['enum'] === undefined) return
if (!Array.isArray(resolvedSchema['enum'])) return
if (resolvedSchema['enum'].length === 0) return

// if the path length is < 4 then skip. This is to avoid infinite
// callstack when handling schema that is a string such as
// "components.schemas.Schema" with "type": "string" and "enum"
if (path.length < 4) return

// If the ending path shows that this is already polymorphic then skip.
// I think this makes it not a problem since our generator considers
// anything polymorphic as a "Model" and the rest of the code just works
// when that is the case.
if (path[path.length - 2] === 'allOf') return
if (path[path.length - 2] === 'anyOf') return
if (path[path.length - 2] === 'oneOf') return

// copy resolvedSchema and remove "type" and "enum"
const copyOfResolvedSchema = { ...resolvedSchema }
delete copyOfResolvedSchema['type']
delete copyOfResolvedSchema['enum']

// save copy of schema then delete all properties on schema
const copyOfSchema = { ...schema }
Object.keys(schema).forEach((key) => delete schema[key])
// Assign allOf with 1 schema that is the original schema
Object.assign(schema, {
allOf: [copyOfSchema],
...copyOfResolvedSchema,
})
})
wrapEnumsInAllOf({ spec })
}

// Ruby generator throws a syntax error for SnapTrade when property of object type is enum w/ default:
// 1) SnapTrade::ModelPortfolio test an instance of ModelPortfolio should create an instance of ModelPortfolio
// Failure/Error: self.model_type = MODEL_TYPE::NMINUS_1
// NameError:
// uninitialized constant SnapTrade::ModelPortfolio::MODEL_TYPE
// self.model_type = MODEL_TYPE::NMINUS_1
//
// To avoid this we transform all in-line enum object type properties into components
if (generator === 'ruby') {
// Ruby generator throws a syntax error for SnapTrade when property of object type is enum w/ default:
// 1) SnapTrade::ModelPortfolio test an instance of ModelPortfolio should create an instance of ModelPortfolio
// Failure/Error: self.model_type = MODEL_TYPE::NMINUS_1
// NameError:
// uninitialized constant SnapTrade::ModelPortfolio::MODEL_TYPE
// self.model_type = MODEL_TYPE::NMINUS_1
//
// To avoid this we transform all in-line enum object type properties into components
recurseObject(spec.spec, ({ value: objectTypeSchema, path }) => {
if (typeof objectTypeSchema !== 'object') return
if (objectTypeSchema === null) return
Expand Down Expand Up @@ -222,6 +170,8 @@ export const transformSpec = async ({
value['$ref'] = `#/components/schemas/${name}`
}
})

wrapEnumsInAllOf({ spec })
}

if (generator === 'python') {
Expand Down Expand Up @@ -1130,6 +1080,65 @@ export const transformSpec = async ({
return serialize({ spec: spec.spec })
}

/**
* Helpful for both TypeScript and Ruby it turns out
*/
function wrapEnumsInAllOf({ spec }: { spec: Spec }): void {
// When using enum strings for some reason wrapping it inside of an allOf with 1 schema fixes an issue
// where the generated data type in TypeScript's README.md is empty
// before:
// ##### lead_type:<a id="lead_type"></a>
// after:
// ##### lead_type: [`LeadType`](./models/lead-type.ts)<a id="lead_type-leadtypemodelslead-typets"></a>

// find all ref objects that are pointing to "type": "string" with an "enum" property
// and wrap them in an allOf with 1 schema
recurseObject(spec.spec, ({ value: schema, path }) => {
if (schema === null) return
if (typeof schema !== 'object') return

// ensure schema has a $ref property
if (!('$ref' in schema)) return

const resolvedSchema = resolveRef({
refOrObject: schema,
$ref: spec.$ref,
})

if (resolvedSchema['type'] !== 'string') return
if (resolvedSchema['enum'] === undefined) return
if (!Array.isArray(resolvedSchema['enum'])) return
if (resolvedSchema['enum'].length === 0) return

// if the path length is < 4 then skip. This is to avoid infinite
// callstack when handling schema that is a string such as
// "components.schemas.Schema" with "type": "string" and "enum"
if (path.length < 4) return

// If the ending path shows that this is already polymorphic then skip.
// I think this makes it not a problem since our generator considers
// anything polymorphic as a "Model" and the rest of the code just works
// when that is the case.
if (path[path.length - 2] === 'allOf') return
if (path[path.length - 2] === 'anyOf') return
if (path[path.length - 2] === 'oneOf') return

// copy resolvedSchema and remove "type" and "enum"
const copyOfResolvedSchema = { ...resolvedSchema }
delete copyOfResolvedSchema['type']
delete copyOfResolvedSchema['enum']

// save copy of schema then delete all properties on schema
const copyOfSchema = { ...schema }
Object.keys(schema).forEach((key) => delete schema[key])
// Assign allOf with 1 schema that is the original schema
Object.assign(schema, {
allOf: [copyOfSchema],
...copyOfResolvedSchema,
})
})
}

function handleAllOfWithNullable({ spec }: { spec: Spec }): void {
// find any object that has "nullable: true" and "allOf" with a single $ref and
// create a new schema that is the name of the referenced schema suffixed with "Nullable"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ default Map<String, Object> transformAdditionalPropertiesToMap(AdditionalPropert
String npmName = additionalProperties.getNpmName();
// set properties to true for all customers except for the ones in the above list
if (existingCustomers.stream().noneMatch(npmName::contains)) {
// Instantiate a Set of Strings for existing customers
// if none of existing customers are a substring of the npmName, then set the
// option to true
putIfPresent(map, "useSecurityKeyNameAsPropertyName", true);
putIfPresent(map, "removeDefaultConfigurationParameters", true);
}
Expand All @@ -372,6 +375,17 @@ default Map<String, Object> transformAdditionalPropertiesToMap(AdditionalPropert
if (generator.equals("php") && existingCustomers.stream().noneMatch(packageName::contains)) {
putIfPresent(map, "useSecurityKeyNameAsPropertyName", true);
}
if (additionalProperties.getGemName() != null) {
String gemName = additionalProperties.getGemName();
if (existingCustomers.stream().noneMatch(gemName::contains)) {
putIfPresent(map, "useSecurityKeyNameAsPropertyName", true);
putIfPresent(map, "removeDefaultConfigurationParameters", true);
// instead of mapping "data", "status_code", "headers", and "response" into a hash, continue
// returning it as a tuple
putIfPresent(map, "useNewWithHttpInfoReturnType", true);
}
}

if (additionalProperties.getObjectPropertyNamingConvention() != null) {
putIfPresent(map, "useCamelCase",
additionalProperties.getObjectPropertyNamingConvention().equals("camelCase"));
Expand Down Expand Up @@ -422,6 +436,9 @@ default Map<String, Object> transformAdditionalPropertiesToMap(AdditionalPropert
putIfPresent(map, "clientName", additionalProperties.getClientName());
if (additionalProperties.getClientName() != null)
putIfPresent(map, "clientNameLowercase", additionalProperties.getClientName().toLowerCase());
if (additionalProperties.getModuleName() != null) {
putIfPresent(map, "clientNameLowercase", additionalProperties.getModuleName().toLowerCase());
}
if ("swift5".equals(generator)) {
putIfPresent(map, "clientNameLowercase", additionalProperties.getProjectName().toLowerCase());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class CodegenOperation {
public String returnBaseType;
public String path, operationId, returnType, returnFormat, httpMethod,
returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse;
public String unescapedNotesWithPounds;
public CodegenDiscriminator discriminator;
public List<Map<String, String>> consumes, produces, prioritizedContentTypes;
public String requestBodyContentType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.openapitools.codegen.utils.CamelizeOption;
import org.openapitools.codegen.utils.StringUtils;

import javax.validation.groups.Default;
import java.util.*;

/**
Expand Down Expand Up @@ -68,7 +69,8 @@ public String paramNameSnakeCase() {
public boolean isStrictlyObject;

public String baseName, paramName, dataType, datatypeWithEnum, dataFormat, contentType,
collectionFormat, description, unescapedDescription, baseType, defaultValue, enumDefaultValue, enumName, style;
collectionFormat, description, baseType, defaultValue, enumDefaultValue, enumName, style;
public String unescapedDescription;
public String dataTypeLowerCase;

// When you need "HashMap" instead of "Map" in Java SDK
Expand Down Expand Up @@ -874,5 +876,38 @@ public boolean getSchemaIsFromAdditionalProperties() {
public void setSchemaIsFromAdditionalProperties(boolean schemaIsFromAdditionalProperties) {
this.schemaIsFromAdditionalProperties = schemaIsFromAdditionalProperties;
}

public String getDescriptionForSingleLine() {
if (unescapedDescription == null) return null;
String normalized = DefaultCodegen.normalizeDescriptionForSingleline(unescapedDescription);
String escaped = DefaultCodegen.escapeTextStatic(normalized);
return escaped;
}

/**
* Takes the description for a single line and wraps it whenever it exceeds the given line length (80 characters).
* @return the description wrapped for multiple lines
*/
public String getDescriptionForMarkdown() {
if (getDescriptionForSingleLine() == null) return null;
/**
* Wrap the description to multiple lines if it exceeds the line length (80 characters).
*/
List<String> lines = new ArrayList<>();
String[] words = getDescriptionForSingleLine().split(" ");
StringBuilder line = new StringBuilder();
for (String word : words) {
if (line.length() + word.length() + 1 > 80) {
lines.add(line.toString());
line = new StringBuilder();
}
if (line.length() > 0) {
line.append(" ");
}
line.append(word);
}
lines.add(line.toString());
return String.join("\n", lines);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,23 @@ public TemplatingEngineAdapter processTemplatingEngine(TemplatingEngineAdapter t
return templatingEngine;
}

/**
*
* @param description
* @return description where substrings like "\n " are replaced with " ".
*/
static public String normalizeDescriptionForSingleline(String description) {
// remove new lines
String desc = description.replaceAll("\\n", " ");
// remove repeating spaces
desc = desc.replaceAll("\\s+", " ");
return desc;
}

public static String escapeTextStatic(String input) {
return new DefaultCodegen().escapeText(input);
}

// override with any special text escaping logic
@Override
@SuppressWarnings("static-method")
Expand Down Expand Up @@ -4601,6 +4618,10 @@ public CodegenOperation fromOperation(String path,
op.operationId = toOperationId(operationId);
op.summary = escapeText(operation.getSummary());
op.unescapedNotes = operation.getDescription();
if (op.unescapedNotes != null) {
op.unescapedNotesWithPounds = Arrays.stream(operation.getDescription().split("\n")).map(s -> "# " + s)
.collect(Collectors.joining("\n"));
}
op.notes = escapeText(operation.getDescription());
op.hasConsumes = false;
op.hasProduces = false;
Expand Down Expand Up @@ -7388,7 +7409,7 @@ public CodegenParameter fromFormProperty(String name, Schema propertySchema, Set

codegenParameter.isFormParam = Boolean.TRUE;
codegenParameter.description = escapeText(codegenProperty.description);
codegenParameter.unescapedDescription = codegenProperty.getDescription();
codegenParameter.unescapedDescription = codegenProperty.getUnescapedDescription();
codegenParameter.jsonSchema = Json.pretty(propertySchema);

if (codegenProperty.getVendorExtensions() != null && !codegenProperty.getVendorExtensions().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,8 @@ Map<String, Object> buildSupportFileBundle(List<OperationsMap> allOperations, Li
bundle.put("podVersionWithoutDashes", ((String) bundle.get("podVersion")).replaceAll("-", ""));
if (bundle.get("artifactVersion") != null)
bundle.put("artifactVersionWithoutDashes", ((String) bundle.get("artifactVersion")).replaceAll("-", ""));
if (bundle.get("gemVersion") != null)
bundle.put("gemVersionWithoutDashes", ((String) bundle.get("gemVersion")).replaceAll("-", ""));

config.postProcessSupportingFileData(bundle);

Expand Down
Loading

0 comments on commit c1ac616

Please sign in to comment.