/// <summary> /// Called to execute the code generator application. /// </summary> /// <param name="args">The command-line arguments.</param> /// <returns>The exit code.</returns> public int Run(IReadOnlyList <string> args) { try { var argsReader = new ArgsReader(args); if (argsReader.ReadHelpFlag()) { foreach (string line in Description) { System.Console.WriteLine(line); } System.Console.WriteLine(); WriteUsage(); return(0); } var generator = CreateGenerator(argsReader); generator.GeneratorName = s_assemblyName; if (SupportsCustomIndent) { string indentText = argsReader.ReadIndentOption(); if (indentText != null) { generator.IndentText = indentText; } } if (SupportsCustomNewLine) { string newLine = argsReader.ReadNewLineOption(); if (newLine != null) { generator.NewLine = newLine; } } string serviceName = argsReader.ReadServiceNameOption(); bool shouldClean = SupportsClean && argsReader.ReadCleanFlag(); bool isQuiet = argsReader.ReadQuietFlag(); bool isVerify = argsReader.ReadVerifyFlag(); bool isDryRun = argsReader.ReadDryRunFlag(); string inputPath = argsReader.ReadArgument(); if (inputPath == null) { throw new ArgsReaderException("Missing input path."); } string outputPath = argsReader.ReadArgument(); if (outputPath == null) { throw new ArgsReaderException("Missing output path."); } argsReader.VerifyComplete(); NamedText input; if (inputPath == "-") { input = new NamedText("", System.Console.In.ReadToEnd()); } else { if (!File.Exists(inputPath)) { throw new ApplicationException("Input file does not exist: " + inputPath); } input = new NamedText(Path.GetFileName(inputPath), File.ReadAllText(inputPath)); } ServiceInfo service; if (ServiceDefinitionUtility.DetectFormat(input) == ServiceDefinitionFormat.Swagger) { service = new SwaggerParser { ServiceName = serviceName }.ParseDefinition(input); } else { if (serviceName != null) { throw new ArgsReaderException("--serviceName not supported for FSD input."); } service = new FsdParser().ParseDefinition(input); } PrepareGenerator(generator, service, outputPath); var output = generator.GenerateOutput(service); if (SupportsSingleOutput && !outputPath.EndsWith("/", StringComparison.Ordinal) && !outputPath.EndsWith("\\", StringComparison.Ordinal) && !Directory.Exists(outputPath)) { if (output.NamedTexts.Count > 1) { throw new InvalidOperationException("Multiple outputs not expected."); } if (output.NamedTexts.Count == 1) { var namedText = output.NamedTexts[0]; if (outputPath == "-") { System.Console.Write(namedText.Text); } else if (ShouldWriteByteOrderMark(namedText.Name)) { File.WriteAllText(outputPath, namedText.Text, s_utf8WithBom); } else { File.WriteAllText(outputPath, namedText.Text); } } } else { var namedTextsToWrite = new List <NamedText>(); foreach (var namedText in output.NamedTexts) { string existingFilePath = Path.Combine(outputPath, namedText.Name); if (File.Exists(existingFilePath)) { // ignore CR when comparing files if (namedText.Text.Replace("\r", "") != File.ReadAllText(existingFilePath).Replace("\r", "")) { namedTextsToWrite.Add(namedText); if (!isQuiet) { System.Console.WriteLine("changed " + namedText.Name); } } } else { namedTextsToWrite.Add(namedText); if (!isQuiet) { System.Console.WriteLine("added " + namedText.Name); } } } var namesToDelete = new List <string>(); if (shouldClean && output.PatternsToClean.Count != 0) { var directoryInfo = new DirectoryInfo(outputPath); if (directoryInfo.Exists) { foreach (string nameMatchingPattern in FindNamesMatchingPatterns(directoryInfo, output.PatternsToClean)) { if (output.NamedTexts.All(x => x.Name != nameMatchingPattern)) { namesToDelete.Add(nameMatchingPattern); if (!isQuiet) { System.Console.WriteLine("removed " + nameMatchingPattern); } } } } } if (isVerify) { return(namedTextsToWrite.Count != 0 || namesToDelete.Count != 0 ? 1 : 0); } if (!isDryRun) { if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } foreach (var namedText in namedTextsToWrite) { string outputFilePath = Path.Combine(outputPath, namedText.Name); string outputFileDirectoryPath = Path.GetDirectoryName(outputFilePath); if (outputFileDirectoryPath != null && outputFileDirectoryPath != outputPath && !Directory.Exists(outputFileDirectoryPath)) { Directory.CreateDirectory(outputFileDirectoryPath); } if (ShouldWriteByteOrderMark(namedText.Name)) { File.WriteAllText(outputFilePath, namedText.Text, s_utf8WithBom); } else { File.WriteAllText(outputFilePath, namedText.Text); } } foreach (string nameToDelete in namesToDelete) { File.Delete(Path.Combine(outputPath, nameToDelete)); } } } return(0); } catch (Exception exception) { if (exception is ApplicationException || exception is ArgsReaderException || exception is ServiceDefinitionException) { System.Console.Error.WriteLine(exception.Message); if (exception is ArgsReaderException) { System.Console.Error.WriteLine(); WriteUsage(); } return(2); } else { System.Console.Error.WriteLine(exception.ToString()); return(3); } } }
/// <summary> /// Parses Swagger (OpenAPI) 2.0 into a service definition. /// </summary> public ServiceInfo ParseDefinition(NamedText source) { if (string.IsNullOrWhiteSpace(source.Text)) { throw new ServiceDefinitionException("Service definition is missing.", new NamedTextPosition(source.Name, 1, 1)); } SwaggerService swaggerService; SwaggerParserContext context; if (!s_detectJsonRegex.IsMatch(source.Text)) { // parse YAML var yamlDeserializer = new DeserializerBuilder() .IgnoreUnmatchedProperties() .WithNamingConvention(new OurNamingConvention()) .Build(); using (var stringReader = new StringReader(source.Text)) { try { swaggerService = yamlDeserializer.Deserialize <SwaggerService>(stringReader); } catch (YamlException exception) { var exceptionError = exception.InnerException?.Message ?? exception.Message; const string errorStart = "): "; int errorStartIndex = exceptionError.IndexOf(errorStart, StringComparison.OrdinalIgnoreCase); if (errorStartIndex != -1) { exceptionError = exceptionError.Substring(errorStartIndex + errorStart.Length); } var exceptionPosition = new NamedTextPosition(source.Name, exception.End.Line, exception.End.Column); throw new ServiceDefinitionException(exceptionError, exceptionPosition); } } if (swaggerService == null) { throw new ServiceDefinitionException("Service definition is missing.", new NamedTextPosition(source.Name, 1, 1)); } context = SwaggerParserContext.FromYaml(source); } else { // parse JSON using (var stringReader = new StringReader(source.Text)) using (var jsonTextReader = new JsonTextReader(stringReader)) { try { swaggerService = JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings).Deserialize <SwaggerService>(jsonTextReader); } catch (JsonException exception) { var exceptionPosition = new NamedTextPosition(source.Name, jsonTextReader.LineNumber, jsonTextReader.LinePosition); throw new ServiceDefinitionException(exception.Message, exceptionPosition); } context = SwaggerParserContext.FromJson(source); } } return(ConvertSwaggerService(swaggerService, context)); }
public async Task <ServiceResult <GenerateResponseDto> > GenerateAsync(GenerateRequestDto request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } try { var input = new NamedText(request.Definition?.Name ?? "", request.Definition?.Text ?? ""); bool isSwagger = ServiceDefinitionUtility.DetectFormat(input) == ServiceDefinitionFormat.Swagger; var service = isSwagger ? new SwaggerParser().ParseDefinition(input) : new FsdParser().ParseDefinition(input); var generatorName = request.Generator?.Name; switch (generatorName) { case "csharp": return(ServiceResult.Success(GenerateCode(() => new CSharpGenerator(), g => g.GenerateOutput(service)))); case "javascript": return(ServiceResult.Success(GenerateCode(() => new JavaScriptGenerator(), g => g.GenerateOutput(service)))); case "typescript": return(ServiceResult.Success(GenerateCode(() => new JavaScriptGenerator { TypeScript = true }, g => g.GenerateOutput(service)))); case "markdown": return(ServiceResult.Success(GenerateCode(() => new MarkdownGenerator(), g => g.GenerateOutput(service)))); case "fsd": return(ServiceResult.Success(GenerateCode(() => new FsdGenerator(), g => g.GenerateOutput(service)))); case "swagger-json": return(ServiceResult.Success(GenerateCode(() => new SwaggerGenerator(), g => g.GenerateOutput(service)))); case "swagger-yaml": return(ServiceResult.Success(GenerateCode(() => new SwaggerGenerator { Yaml = true }, g => g.GenerateOutput(service)))); case "asp-net-web-api": return(ServiceResult.Success(GenerateCode(() => new AspNetGenerator(), g => g.GenerateOutput(service)))); case "crash": throw new InvalidOperationException("Intentional exception for diagnostic purposes."); default: return(ServiceResult.Failure(ServiceErrors.CreateInvalidRequest($"Unrecognized generator '{generatorName}'."))); } } catch (ServiceDefinitionException exception) { return(ServiceResult.Success(new GenerateResponseDto { Failure = new FailureDto { Message = exception.Error, Line = exception.Position.LineNumber, Column = exception.Position.ColumnNumber, }, })); } }
public static SwaggerParserContext FromJson(NamedText namedText) { return(new SwaggerParserContext(namedText, isYaml: false)); }
private SwaggerParserContext(NamedText namedText, bool isYaml, string path = null) { m_namedText = namedText; m_isYaml = isYaml; m_path = path; }
public static SwaggerParserContext FromYaml(NamedText namedText) { return(new SwaggerParserContext(namedText, isYaml: true)); }
public Context(NamedText source, IReadOnlyDictionary <string, FsdRemarksSection> remarksSections) { m_source = source; m_remarksSections = remarksSections; }
public static ServiceInfo ParseDefinition(NamedText source, IReadOnlyDictionary <string, FsdRemarksSection> remarksSections) { return(DefinitionParser(new Context(source, remarksSections)).Parse(source.Text)); }
/// <summary> /// Parses an FSD file into a service definition. /// </summary> public ServiceInfo ParseDefinition(NamedText source) { IReadOnlyList <string> definitionLines = null; var remarksSections = new Dictionary <string, FsdRemarksSection>(StringComparer.OrdinalIgnoreCase); // read remarks after definition using (var reader = new StringReader(source.Text)) { string name = null; var lines = new List <string>(); int lineNumber = 0; int headingLineNumber = 0; while (true) { string line = reader.ReadLine(); lineNumber++; Match match = line == null ? null : s_markdownHeading.Match(line); if (match == null || match.Success) { if (name == null) { definitionLines = lines; } else { while (lines.Count != 0 && string.IsNullOrWhiteSpace(lines[0])) { lines.RemoveAt(0); } while (lines.Count != 0 && string.IsNullOrWhiteSpace(lines[lines.Count - 1])) { lines.RemoveAt(lines.Count - 1); } var position = new NamedTextPosition(source.Name, headingLineNumber, 1); if (remarksSections.ContainsKey(name)) { throw new ServiceDefinitionException("Duplicate remarks heading: " + name, position); } remarksSections.Add(name, new FsdRemarksSection(name, lines, position)); } if (match == null) { break; } name = line.Substring(match.Index + match.Length).Trim(); lines = new List <string>(); headingLineNumber = lineNumber; } else { lines.Add(line); } } } source = new NamedText(source.Name, string.Join("\n", definitionLines)); ServiceInfo service; try { service = FsdParsers.ParseDefinition(source, remarksSections); } catch (ParseException exception) { var expectation = exception .Result .GetNamedFailures() .Distinct() .GroupBy(x => x.Position) .Select(x => new { LineColumn = x.Key.GetLineColumn(), Names = x.Select(y => y.Name) }) .OrderByDescending(x => x.LineColumn.LineNumber) .ThenByDescending(x => x.LineColumn.ColumnNumber) .First(); throw new ServiceDefinitionException( "expected " + string.Join(" or ", expectation.Names.Distinct().OrderBy(GetExpectationNameRank).ThenBy(x => x, StringComparer.Ordinal)), new NamedTextPosition(source.Name, expectation.LineColumn.LineNumber, expectation.LineColumn.ColumnNumber), exception); } // check for unused remarks sections foreach (var remarksSection in remarksSections.Values) { string sectionName = remarksSection.Name; if (service.Name != sectionName && service.FindMember(sectionName) == null) { throw new ServiceDefinitionException($"Unused remarks heading: {sectionName}", remarksSection.Position); } } return(service); }
/// <summary> /// Creates a single-output instance. /// </summary> public CodeGenOutput(NamedText namedText) : this(namedTexts : namedText == null ? null : new[] { namedText }, patternsToClean : null) { }