private void TransformModelTypes(CodeModelGo cmg) { foreach (var ctg in cmg.ModelTypes.Cast <CompositeTypeGo>()) { var name = ctg.Name.FixedValue.TrimPackageName(cmg.Namespace); // ensure that the candidate name isn't already in use if (name != ctg.Name && cmg.ModelTypes.Any(mt => mt.Name == name)) { name = $"{name}Type"; } if (CodeNamerGo.Instance.UserDefinedNames.Contains(name)) { name = $"{name}{cmg.Namespace.Capitalize()}"; } ctg.SetName(name); } // Find all methods that returned paged results cmg.Methods.Cast <MethodGo>() .Where(m => m.IsPageable).ToList() .ForEach(m => { if (!cmg.PagedTypes.ContainsKey(m.ReturnValue().Body)) { cmg.PagedTypes.Add(m.ReturnValue().Body, m.NextLink); } if (!m.NextMethodExists(cmg.Methods.Cast <MethodGo>())) { cmg.NextMethodUndefined.Add(m.ReturnValue().Body); } }); // Mark all models returned by one or more methods and note any "next link" fields used with paged data cmg.ModelTypes.Cast <CompositeTypeGo>() .Where(mtm => { return(cmg.Methods.Cast <MethodGo>().Any(m => m.HasReturnValue() && m.ReturnValue().Body.Equals(mtm))); }).ToList() .ForEach(mtm => { mtm.IsResponseType = true; if (cmg.PagedTypes.ContainsKey(mtm)) { mtm.NextLink = CodeNamerGo.PascalCaseWithoutChar(cmg.PagedTypes[mtm], '.'); mtm.PreparerNeeded = cmg.NextMethodUndefined.Contains(mtm); } }); }
private void FixStutteringTypeNames(CodeModelGo cmg) { // Trim the package name from exported types; append a suitable qualifier, if needed, to avoid conflicts. var exportedTypes = new HashSet <object>(); exportedTypes.UnionWith(cmg.EnumTypes); exportedTypes.UnionWith(cmg.Methods); exportedTypes.UnionWith(cmg.ModelTypes); var stutteringTypes = exportedTypes .Where(exported => (exported is IModelType && (exported as IModelType).Name.FixedValue.StartsWith(cmg.Namespace, StringComparison.OrdinalIgnoreCase)) || (exported is Method && (exported as Method).Name.FixedValue.StartsWith(cmg.Namespace, StringComparison.OrdinalIgnoreCase))); if (stutteringTypes.Any()) { Logger.Instance.Log(Category.Warning, string.Format(CultureInfo.InvariantCulture, Resources.NamesStutter, stutteringTypes.Count())); stutteringTypes.ForEach(exported => { var name = exported is IModelType ? (exported as IModelType).Name : (exported as Method).Name; Logger.Instance.Log(Category.Warning, string.Format(CultureInfo.InvariantCulture, Resources.StutteringName, name)); name = name.FixedValue.TrimPackageName(cmg.Namespace); var nameInUse = exportedTypes .Any(et => (et is IModelType && (et as IModelType).Name.Equals(name)) || (et is Method && (et as Method).Name.Equals(name))); if (exported is EnumType) { (exported as EnumType).Name.FixedValue = CodeNamerGo.AttachTypeName(name, cmg.Namespace, nameInUse, "Enum"); } else if (exported is CompositeType) { (exported as CompositeType).Name.FixedValue = CodeNamerGo.AttachTypeName(name, cmg.Namespace, nameInUse, "Type"); } else if (exported is Method) { (exported as Method).Name = CodeNamerGo.AttachTypeName(name, cmg.Namespace, nameInUse, "Method"); } }); } }
private static void FixStutteringTypeNames(CodeModelGo cmg) { // Trim the package name from exported types; append a suitable qualifier, if needed, to avoid conflicts. var exportedTypes = new HashSet <object>(); exportedTypes.UnionWith(cmg.EnumTypes); exportedTypes.UnionWith(cmg.Methods); exportedTypes.UnionWith(cmg.ModelTypes); var stutteringTypes = exportedTypes .Where(exported => (exported is IModelType modelType && modelType.Name.FixedValue.StartsWith(cmg.Namespace, StringComparison.OrdinalIgnoreCase)) || (exported is Method method && method.Name.FixedValue.StartsWith(cmg.Namespace, StringComparison.OrdinalIgnoreCase))); if (stutteringTypes.Any()) { stutteringTypes.ForEach(exported => { var name = exported is IModelType type ? type.Name : ((Method)exported).Name; name = name.Value.TrimPackageName(cmg.Namespace); var nameInUse = exportedTypes .Any(et => (et is IModelType modeltype && modeltype.Name.Equals(name)) || (et is Method methodType && methodType.Name.Equals(name))); if (exported is EnumType enumType) { enumType.Name.Value = CodeNamerGo.AttachTypeName(name, cmg.Namespace, nameInUse, "Enum"); } else if (exported is CompositeType compositeType) { compositeType.Name.Value = CodeNamerGo.AttachTypeName(name, cmg.Namespace, nameInUse, "Type"); } else if (exported is Method methodType) { methodType.Name.Value = CodeNamerGo.AttachTypeName(name, cmg.Namespace, nameInUse, "Method"); } }); } }
/// <summary> /// Generates Go code for service client. /// </summary> /// <param name="serviceClient"></param> /// <returns></returns> public override async Task Generate(CodeModel cm) { var folder = Path.GetFullPath(Settings.Instance.Host.GetValue <string>("output-folder").Result).Replace('\\', '/'); // check if the namespace contains illegal characters var ns = Settings.Instance.Host.GetValue <string>("namespace").Result; if (Settings.Instance.Host.GetValue <bool>("namespace-chk").Result) { NamespaceCheck(ns); } // if preview-chk:true is specified verify that preview swagger is output under a preview subdirectory. // this is a bit of a hack until we have proper support for this in the swagger->sdk bot so it's opt-in. if (Settings.Instance.Host.GetValue <bool>("preview-chk").Result) { PreviewCheck(folder); } var codeModel = cm as CodeModelGo; if (codeModel == null) { throw new Exception("Code model is not a Go Code Model"); } // unfortunately there is an ordering issue here. during model generation we might // flatten some types (but not all depending on type). then during client generation // the validation codegen needs to know if a type was flattened so it can generate // the correct code, so we need to generate models before clients. // Models var modelsTemplate = new ModelsTemplate { Model = codeModel }; await Write(modelsTemplate, FormatFileName("models")); // Enums - while initially enums should be part of the models, to decrease the size of the models.go file, // we separate the enums definitions out of the models.go file var enums = codeModel.EnumTypes.Cast <EnumTypeGo>().ToList(); if (enums.Any()) { var enumsTemplate = new EnumsTemplate { Model = codeModel }; await Write(enumsTemplate, FormatFileName("enums")); } // Service client var serviceClientTemplate = new ServiceClientTemplate { Model = codeModel }; await Write(serviceClientTemplate, FormatFileName("client")); // by convention the methods in the method group with an empty // name go into the client template so skip them here. HashSet <string> ReservedFiles = new HashSet <string>(StringComparer.OrdinalIgnoreCase) { "models", "client", "version", "interfaces", "enums", }; foreach (var methodGroup in codeModel.MethodGroups.Where(mg => !string.IsNullOrEmpty(mg.Name))) { if (ReservedFiles.Contains(methodGroup.Name.Value)) { methodGroup.Name += "group"; } ReservedFiles.Add(methodGroup.Name); var methodGroupTemplate = new MethodGroupTemplate { Model = methodGroup }; await Write(methodGroupTemplate, FormatFileName(methodGroup.Name).ToLowerInvariant()); } // interfaces var interfacesTemplate = new InterfacesTemplate { Model = codeModel }; await Write(interfacesTemplate, FormatFileName($"{CodeNamerGo.InterfacePackageName(codeModel.Namespace)}/interfaces")); // Version var versionTemplate = new VersionTemplate { Model = codeModel }; await Write(versionTemplate, FormatFileName("version")); // go.mod file, opt-in by specifying the gomod-root arg var modRoot = Settings.Instance.Host.GetValue <string>("gomod-root").Result; if (!string.IsNullOrWhiteSpace(modRoot)) { var i = folder.IndexOf(modRoot); if (i == -1) { throw new Exception($"didn't find module root '{modRoot}' in output path '{folder}'"); } var goVersion = Settings.Instance.Host.GetValue <string>("go-version").Result; if (string.IsNullOrWhiteSpace(goVersion)) { goVersion = defaultGoVersion; } // module name is everything to the right of the start of the module root var gomodTemplate = new GoModTemplate { Model = new GoMod(folder.Substring(i), goVersion) }; await Write(gomodTemplate, $"{StagingDir()}go.mod"); } // metadata var metadataOutputFolder = Settings.Instance.Host.GetValue <string>("metadata-output-folder").Result; if (!string.IsNullOrWhiteSpace(metadataOutputFolder)) { var metadataTemplate = new MetadataTemplate { Model = new MetadataGo(Settings.Instance.Host.GetValue <string[]>("input-file").Result, folder, ns) }; var tag = Settings.Instance.Host.GetValue <string>("tag").Result; await Write(metadataTemplate, $"{metadataOutputFolder}/{tag}.json"); } }
/// <summary> /// Generates Go code for service client. /// </summary> /// <param name="serviceClient"></param> /// <returns></returns> public override async Task Generate(CodeModel cm) { // if preview-chk:true is specified verify that preview swagger is output under a preview subdirectory. // this is a bit of a hack until we have proper support for this in the swagger->sdk bot so it's opt-in. if (Settings.Instance.Host.GetValue <bool>("preview-chk").Result) { const string previewSubdir = "/preview/"; var files = await Settings.Instance.Host.GetValue <string[]>("input-file"); // only evaluate composite builds if all swaggers are preview as we don't have a well-defined model for mixed preview/stable swaggers if (files.All(file => file.IndexOf(previewSubdir) > -1) && Settings.Instance.Host.GetValue <string>("output-folder").Result.IndexOf(previewSubdir) == -1) { throw new InvalidOperationException($"codegen for preview swagger {files[0]} must be under a preview subdirectory"); } } var codeModel = cm as CodeModelGo; if (codeModel == null) { throw new Exception("Code model is not a Go Code Model"); } // unfortunately there is an ordering issue here. during model generation we might // flatten some types (but not all depending on type). then during client generation // the validation codegen needs to know if a type was flattened so it can generate // the correct code, so we need to generate models before clients. // Models var modelsTemplate = new ModelsTemplate { Model = codeModel }; await Write(modelsTemplate, FormatFileName("models")); // Service client var serviceClientTemplate = new ServiceClientTemplate { Model = codeModel }; await Write(serviceClientTemplate, FormatFileName("client")); // by convention the methods in the method group with an empty // name go into the client template so skip them here. HashSet <string> ReservedFiles = new HashSet <string>(StringComparer.OrdinalIgnoreCase) { "models", "client", "version", "interfaces", }; foreach (var methodGroup in codeModel.MethodGroups.Where(mg => !string.IsNullOrEmpty(mg.Name))) { if (ReservedFiles.Contains(methodGroup.Name.Value)) { methodGroup.Name += "group"; } ReservedFiles.Add(methodGroup.Name); var methodGroupTemplate = new MethodGroupTemplate { Model = methodGroup }; await Write(methodGroupTemplate, FormatFileName(methodGroup.Name).ToLowerInvariant()); } // interfaces var interfacesTemplate = new InterfacesTemplate { Model = codeModel }; await Write(interfacesTemplate, FormatFileName($"{CodeNamerGo.InterfacePackageName(codeModel.Namespace)}/interfaces")); // Version var versionTemplate = new VersionTemplate { Model = codeModel }; await Write(versionTemplate, FormatFileName("version")); // go.mod file, opt-in by specifying the gomod-root arg var modRoot = Settings.Instance.Host.GetValue <string>("gomod-root").Result; if (!string.IsNullOrWhiteSpace(modRoot)) { var normalized = Settings.Instance.Host.GetValue <string>("output-folder").Result.Replace('\\', '/'); var i = normalized.IndexOf(modRoot); if (i == -1) { throw new Exception($"didn't find module root '{modRoot}' in output path '{normalized}'"); } // module name is everything to the right of the start of the module root var gomodTemplate = new GoModTemplate { Model = new GoMod(normalized.Substring(i)) }; await Write(gomodTemplate, $"{StagingDir()}go.mod"); } }