From 82c2edce0e00ec21172979c97cd157d61d639e43 Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Sat, 3 Dec 2022 18:53:29 +0100 Subject: [PATCH] Initial project setup --- .config/dotnet-tools.json | 5 + .editorconfig | 364 ++++++++ .gitignore | 480 +++++++++++ Bismarck.NET.sln | 36 + .../PetStoreApi/Controllers/PetEndpoints.cs | 12 + .../Controllers/WeatherForecastController.cs | 32 + examples/PetStoreApi/PetStoreApi.csproj | 25 + examples/PetStoreApi/Program.cs | 25 + .../Properties/launchSettings.json | 41 + examples/PetStoreApi/WeatherForecast.cs | 12 + examples/PetStoreApi/api/PetStoreApi.yaml | 809 ++++++++++++++++++ .../PetStoreApi/appsettings.Development.json | 8 + examples/PetStoreApi/appsettings.json | 9 + global.json | 5 + .../Bismarck.CodeGenerator.csproj | 48 ++ .../BismarckCodeGenerator.props | 6 + src/Bismarck.CodeGenerator/Constants.cs | 7 + .../ContractGenerator.cs | 107 +++ .../Extensions/OperationNameExtensions.cs | 39 + .../ResourceStreamLoaderExtensions.cs | 11 + .../GeneratorContext.cs | 33 + .../Generators/EnumGenerator.cs | 43 + .../Generators/ModelGenerator.cs | 90 ++ .../Generators/SpecBase.cs | 8 + .../Models/GeneratorSpec.cs | 10 + .../Models/Properties.cs | 24 + .../templates/Enum.tmplcs | 8 + .../templates/OperationInterface.tmplcs | 8 + .../templates/SchemaModel.tmplcs | 8 + 29 files changed, 2313 insertions(+) create mode 100644 .config/dotnet-tools.json create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 Bismarck.NET.sln create mode 100644 examples/PetStoreApi/Controllers/PetEndpoints.cs create mode 100644 examples/PetStoreApi/Controllers/WeatherForecastController.cs create mode 100644 examples/PetStoreApi/PetStoreApi.csproj create mode 100644 examples/PetStoreApi/Program.cs create mode 100644 examples/PetStoreApi/Properties/launchSettings.json create mode 100644 examples/PetStoreApi/WeatherForecast.cs create mode 100644 examples/PetStoreApi/api/PetStoreApi.yaml create mode 100644 examples/PetStoreApi/appsettings.Development.json create mode 100644 examples/PetStoreApi/appsettings.json create mode 100644 global.json create mode 100644 src/Bismarck.CodeGenerator/Bismarck.CodeGenerator.csproj create mode 100644 src/Bismarck.CodeGenerator/BismarckCodeGenerator.props create mode 100644 src/Bismarck.CodeGenerator/Constants.cs create mode 100644 src/Bismarck.CodeGenerator/ContractGenerator.cs create mode 100644 src/Bismarck.CodeGenerator/Extensions/OperationNameExtensions.cs create mode 100644 src/Bismarck.CodeGenerator/Extensions/ResourceStreamLoaderExtensions.cs create mode 100644 src/Bismarck.CodeGenerator/GeneratorContext.cs create mode 100644 src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs create mode 100644 src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs create mode 100644 src/Bismarck.CodeGenerator/Generators/SpecBase.cs create mode 100644 src/Bismarck.CodeGenerator/Models/GeneratorSpec.cs create mode 100644 src/Bismarck.CodeGenerator/Models/Properties.cs create mode 100644 src/Bismarck.CodeGenerator/templates/Enum.tmplcs create mode 100644 src/Bismarck.CodeGenerator/templates/OperationInterface.tmplcs create mode 100644 src/Bismarck.CodeGenerator/templates/SchemaModel.tmplcs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..b0e38ab --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "isRoot": true, + "tools": {} +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b5f39e6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,364 @@ +root = true + +# All files +[*] +indent_style = space + +# Xml files +[*.xml] +indent_size = 2 + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### +[*.{cs,vb}] + +# Organize usings +dotnet_separate_import_directive_groups = true +dotnet_sort_system_directives_first = true +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:warning + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +#### C# Coding Conventions #### +[*.cs] + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### +[*.{cs,vb}] + +# Naming rules + +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion +dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces +dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase + +dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion +dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters +dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase + +dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods +dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties +dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.events_should_be_pascalcase.symbols = events +dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables +dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase + +dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants +dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase + +dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion +dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters +dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase + +dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields +dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion +dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields +dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase + +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase + +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums +dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase + +# Symbol specifications + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interfaces.required_modifiers = + +dotnet_naming_symbols.enums.applicable_kinds = enum +dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enums.required_modifiers = + +dotnet_naming_symbols.events.applicable_kinds = event +dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.events.required_modifiers = + +dotnet_naming_symbols.methods.applicable_kinds = method +dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.methods.required_modifiers = + +dotnet_naming_symbols.properties.applicable_kinds = property +dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.properties.required_modifiers = + +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_fields.required_modifiers = + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_fields.required_modifiers = + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum +dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types_and_namespaces.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +dotnet_naming_symbols.type_parameters.applicable_kinds = namespace +dotnet_naming_symbols.type_parameters.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters.required_modifiers = + +dotnet_naming_symbols.private_constant_fields.applicable_kinds = field +dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_variables.applicable_kinds = local +dotnet_naming_symbols.local_variables.applicable_accessibilities = local +dotnet_naming_symbols.local_variables.required_modifiers = + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.applicable_accessibilities = local +dotnet_naming_symbols.local_constants.required_modifiers = const + +dotnet_naming_symbols.parameters.applicable_kinds = parameter +dotnet_naming_symbols.parameters.applicable_accessibilities = * +dotnet_naming_symbols.parameters.required_modifiers = + +dotnet_naming_symbols.public_constant_fields.applicable_kinds = field +dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_constant_fields.required_modifiers = const + +dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function +dotnet_naming_symbols.local_functions.applicable_accessibilities = * +dotnet_naming_symbols.local_functions.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.capitalization = pascal_case + +dotnet_naming_style.ipascalcase.required_prefix = I +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.capitalization = pascal_case + +dotnet_naming_style.tpascalcase.required_prefix = T +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.capitalization = pascal_case + +dotnet_naming_style._camelcase.required_prefix = _ +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.capitalization = camel_case + +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.capitalization = camel_case + +dotnet_naming_style.s_camelcase.required_prefix = s_ +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.capitalization = camel_case + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ab4cec --- /dev/null +++ b/.gitignore @@ -0,0 +1,480 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +.idea/ \ No newline at end of file diff --git a/Bismarck.NET.sln b/Bismarck.NET.sln new file mode 100644 index 0000000..7887ee8 --- /dev/null +++ b/Bismarck.NET.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C783C00E-C98E-46A0-A8F2-5AEA26A83ABD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bismarck.CodeGenerator", "src\Bismarck.CodeGenerator\Bismarck.CodeGenerator.csproj", "{A1DE0284-6376-4D0C-A18D-9065544FC532}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{C0C20B96-58A8-4ED9-964D-4DA4A0CE638E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PetStoreApi", "examples\PetStoreApi\PetStoreApi.csproj", "{527811DB-DB1C-40F7-AB9F-313DEFFEEA7D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1DE0284-6376-4D0C-A18D-9065544FC532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1DE0284-6376-4D0C-A18D-9065544FC532}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1DE0284-6376-4D0C-A18D-9065544FC532}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1DE0284-6376-4D0C-A18D-9065544FC532}.Release|Any CPU.Build.0 = Release|Any CPU + {527811DB-DB1C-40F7-AB9F-313DEFFEEA7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {527811DB-DB1C-40F7-AB9F-313DEFFEEA7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {527811DB-DB1C-40F7-AB9F-313DEFFEEA7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {527811DB-DB1C-40F7-AB9F-313DEFFEEA7D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A1DE0284-6376-4D0C-A18D-9065544FC532} = {C783C00E-C98E-46A0-A8F2-5AEA26A83ABD} + {527811DB-DB1C-40F7-AB9F-313DEFFEEA7D} = {C0C20B96-58A8-4ED9-964D-4DA4A0CE638E} + EndGlobalSection +EndGlobal diff --git a/examples/PetStoreApi/Controllers/PetEndpoints.cs b/examples/PetStoreApi/Controllers/PetEndpoints.cs new file mode 100644 index 0000000..e2f9ae4 --- /dev/null +++ b/examples/PetStoreApi/Controllers/PetEndpoints.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PetStoreApi.Controllers; + +public class PetEndpoints : ControllerBase +{ + public Task OnUpdatePetAsync() + { + var p = new Pet { Id = 1 }; + return Task.FromResult(Ok() as IActionResult); + } +} \ No newline at end of file diff --git a/examples/PetStoreApi/Controllers/WeatherForecastController.cs b/examples/PetStoreApi/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..5165b73 --- /dev/null +++ b/examples/PetStoreApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PetStoreApi.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public Task Get() + { + return Task.FromResult(Ok(Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray()) as IActionResult); + } +} \ No newline at end of file diff --git a/examples/PetStoreApi/PetStoreApi.csproj b/examples/PetStoreApi/PetStoreApi.csproj new file mode 100644 index 0000000..9cc60f3 --- /dev/null +++ b/examples/PetStoreApi/PetStoreApi.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + true + + + + + + + + + + + + + + + + + + diff --git a/examples/PetStoreApi/Program.cs b/examples/PetStoreApi/Program.cs new file mode 100644 index 0000000..8264bac --- /dev/null +++ b/examples/PetStoreApi/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/examples/PetStoreApi/Properties/launchSettings.json b/examples/PetStoreApi/Properties/launchSettings.json new file mode 100644 index 0000000..a67d144 --- /dev/null +++ b/examples/PetStoreApi/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:11599", + "sslPort": 44367 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5274", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7232;http://localhost:5274", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/PetStoreApi/WeatherForecast.cs b/examples/PetStoreApi/WeatherForecast.cs new file mode 100644 index 0000000..a475b00 --- /dev/null +++ b/examples/PetStoreApi/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace PetStoreApi; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/examples/PetStoreApi/api/PetStoreApi.yaml b/examples/PetStoreApi/api/PetStoreApi.yaml new file mode 100644 index 0000000..0105e43 --- /dev/null +++ b/examples/PetStoreApi/api/PetStoreApi.yaml @@ -0,0 +1,809 @@ +openapi: 3.0.2 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: "This is a sample Pet Store Server based on the OpenAPI 3.0 specification.\ + \ You can find out more about\nSwagger at [http://swagger.io](http://swagger.io).\ + \ In the third iteration of the pet store, we've switched to the design first\ + \ approach!\nYou can now help us improve the API whether it's by making changes\ + \ to the definition itself or to the code.\nThat way, with time, we can improve\ + \ the API in general, and expose some of the new features in OAS3.\n\nSome useful\ + \ links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n\ + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)" + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.17 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: /api/v3 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: http://swagger.io + - name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "405": + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "405": + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + default: available + externalDocs: + url: https://dev.azure.com + x-api-type: DeclarativeRouter.Models.PetStatus + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: "" + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "405": + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + description: "" + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + description: "" + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "405": + description: Invalid input + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid ID supplied + "404": + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: "" + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: "" + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + /user/{username}: + get: + tags: + - user + summary: Get user by user name + description: "" + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + put: + tags: + - user + summary: Update user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "400": + description: Invalid username supplied + "404": + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + x-bismarck-type-name/PetStoreApi: OrderStatus + complete: + type: boolean + xml: + name: order + Customer: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: '#/components/schemas/Address' + xml: + name: customer + Address: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: "94301" + xml: + name: address + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + externalDocs: + url: https://dev.azure.com/ + x-api-type: DeclarativeRouter.Models.Pet + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/examples/PetStoreApi/appsettings.Development.json b/examples/PetStoreApi/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/examples/PetStoreApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/examples/PetStoreApi/appsettings.json b/examples/PetStoreApi/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/examples/PetStoreApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/global.json b/global.json new file mode 100644 index 0000000..08585a2 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "7.0.100" + } +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Bismarck.CodeGenerator.csproj b/src/Bismarck.CodeGenerator/Bismarck.CodeGenerator.csproj new file mode 100644 index 0000000..80d036c --- /dev/null +++ b/src/Bismarck.CodeGenerator/Bismarck.CodeGenerator.csproj @@ -0,0 +1,48 @@ + + + + netstandard2.1 + enable + enable + default + Bismarck.CodeGenerator + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + + + diff --git a/src/Bismarck.CodeGenerator/BismarckCodeGenerator.props b/src/Bismarck.CodeGenerator/BismarckCodeGenerator.props new file mode 100644 index 0000000..1c02293 --- /dev/null +++ b/src/Bismarck.CodeGenerator/BismarckCodeGenerator.props @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Constants.cs b/src/Bismarck.CodeGenerator/Constants.cs new file mode 100644 index 0000000..5a167fe --- /dev/null +++ b/src/Bismarck.CodeGenerator/Constants.cs @@ -0,0 +1,7 @@ +namespace Bismarck.CodeGenerator; + +internal static class Constants +{ + internal const string RootNamespaceKey = "build_property.rootnamespace"; + internal const string OperationInterface = nameof(OperationInterface); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/ContractGenerator.cs b/src/Bismarck.CodeGenerator/ContractGenerator.cs new file mode 100644 index 0000000..216255b --- /dev/null +++ b/src/Bismarck.CodeGenerator/ContractGenerator.cs @@ -0,0 +1,107 @@ +using System.Diagnostics; + +using Bismarck.CodeGenerator.Extensions; +using Bismarck.CodeGenerator.Generators; +using Bismarck.CodeGenerator.Models; + +using Microsoft.CodeAnalysis; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; + +using Scriban; + +using static Bismarck.CodeGenerator.Constants; + + +namespace Bismarck.CodeGenerator; + +[Generator] +public class ContractGenerator : ISourceGenerator +{ + private static readonly IReadOnlyDictionary TemplateNames = new Dictionary + { + { + OperationInterface, "templates.OperationInterface.tmplcs" + } + }; + + private string _rootNamespace; + private readonly IDictionary _templates; + private readonly ModelGenerator _modelGenerator; + + public ContractGenerator() + { + _rootNamespace = string.Empty; + _templates = new Dictionary(); + _modelGenerator = new ModelGenerator(); + } + + public void Initialize(GeneratorInitializationContext context) + { + /*while (!Debugger.IsAttached) + { + Thread.Sleep(1000); + }*/ + + var assembly = typeof(ContractGenerator).Assembly; + foreach (var kv in TemplateNames) + { + using var stream = assembly.GetManifestResourceStream(typeof(ContractGenerator), kv.Value); + using var reader = new StreamReader(stream!); + _templates[kv.Key] = Template.Parse(reader.ReadToEnd()); + } + } + + public void Execute(GeneratorExecutionContext context) + { + if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(RootNamespaceKey, out _rootNamespace)) + { + throw new ArgumentException("Couldn't retrieve root namespace"); + } + + var ctx = new GeneratorContext(context); + + foreach ((OpenApiDocument doc, string serviceName) in ReadSpecs(context)) + { + _modelGenerator.GenerateModelSchema(ctx, doc.Components.Schemas, _rootNamespace); + foreach (var path in doc.Paths) + { + foreach (var openApiOperation in path.Value.Operations) + { + openApiOperation.Value.OperationId = openApiOperation.Value.OperationId.SanitizeTypeName(); + Debug.WriteLine($"Path: {path.Key}"); + Debug.WriteLine($"Method: {openApiOperation.Key}"); + Debug.WriteLine($"Operation id: {openApiOperation.Value.OperationId}"); + + var rendered = _templates[OperationInterface].Render(new + { + Namespace = _rootNamespace, + OperationName = openApiOperation.Value.OperationId + }); + + context.AddSource($"I{openApiOperation.Value.OperationId}.g.cs", rendered); + } + } + } + } + + private IEnumerable ReadSpecs(GeneratorExecutionContext context) + { + foreach (var file in context.AdditionalFiles) + { + if (!context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.Service", out var serviceName)) + { + continue; + } + + var reader = new OpenApiStreamReader(); + OpenApiDocument? document; + using (var stream = File.OpenRead(file.Path)) + { + document = reader.Read(stream, out _); + } + + yield return new GeneratorSpec(document, serviceName); + } + } +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Extensions/OperationNameExtensions.cs b/src/Bismarck.CodeGenerator/Extensions/OperationNameExtensions.cs new file mode 100644 index 0000000..71509bb --- /dev/null +++ b/src/Bismarck.CodeGenerator/Extensions/OperationNameExtensions.cs @@ -0,0 +1,39 @@ +using System.Text; + +namespace Bismarck.CodeGenerator.Extensions; + +internal static class OperationNameExtensions +{ + internal static string SanitizeTypeName(this string operationId) + { + var builder = new StringBuilder(); + int offset = 0; + + for (var i = 0; + i < operationId.Length; + i++) + { + if (!char.IsLetter(operationId[i])) + { + continue; + } + + builder.Append(char.ToUpperInvariant(operationId[i])); + offset = i + 1; + break; + } + + for (var i = offset; + i < operationId.Length; + i++) + { + char c = operationId[i]; + if (char.IsLetterOrDigit(c)) + { + builder.Append(c); + } + } + + return builder.ToString(); + } +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Extensions/ResourceStreamLoaderExtensions.cs b/src/Bismarck.CodeGenerator/Extensions/ResourceStreamLoaderExtensions.cs new file mode 100644 index 0000000..b29794c --- /dev/null +++ b/src/Bismarck.CodeGenerator/Extensions/ResourceStreamLoaderExtensions.cs @@ -0,0 +1,11 @@ +namespace Bismarck.CodeGenerator.Extensions; + +internal static class ResourceStreamLoaderExtensions +{ + internal static string LoadRelativeResource(this Type t, params string[] relativePath) + { + using var stream = t.Assembly.GetManifestResourceStream(t, string.Join('.', relativePath)); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/GeneratorContext.cs b/src/Bismarck.CodeGenerator/GeneratorContext.cs new file mode 100644 index 0000000..47d4fbb --- /dev/null +++ b/src/Bismarck.CodeGenerator/GeneratorContext.cs @@ -0,0 +1,33 @@ +using System.Collections.Concurrent; + +using Bismarck.CodeGenerator.Models; + +using Microsoft.CodeAnalysis; + +namespace Bismarck.CodeGenerator; + +public class GeneratorContext +{ + private readonly GeneratorExecutionContext _executionContext; + private readonly IDictionary<(SchemaKind, string), bool> _generatedTypes; + + public GeneratorContext(GeneratorExecutionContext executionContext) + { + _executionContext = executionContext; + _generatedTypes = new ConcurrentDictionary<(SchemaKind, string), bool>(); + } + + public void AddSource(SchemaKind kind, string name, string source) + { + if (TypeAlreadyGenerated(kind, name)) + { + return; + } + _executionContext.AddSource($"{name}.g.cs", source); + _generatedTypes.Add((kind, name), true); + } + + public bool TypeAlreadyGenerated(SchemaKind kind, string name) => _generatedTypes.ContainsKey((kind, name)); + + public bool TryAddGeneratedType(SchemaKind kind, string name) => _generatedTypes.TryAdd((kind, name), true); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs b/src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs new file mode 100644 index 0000000..86bd43c --- /dev/null +++ b/src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs @@ -0,0 +1,43 @@ +using Bismarck.CodeGenerator.Extensions; +using Bismarck.CodeGenerator.Models; + +using Microsoft.CodeAnalysis; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; + +using Scriban; + +namespace Bismarck.CodeGenerator.Generators; + +internal record EnumSpec(string Namespace, string TargetName, IEnumerable Values) : SpecBase(Namespace, TargetName) +{ + public IEnumerable Values { get; } = Values; +} + +internal class EnumGenerator +{ + private readonly Template _enumTemplate; + + public EnumGenerator() + { + _enumTemplate = Template.Parse(typeof(ContractGenerator).LoadRelativeResource("templates", "Enum.tmplcs")); + } + + public void Generate(GeneratorContext context, OpenApiSchema schema, string namespaceName, string name) + { + context.AddSource(SchemaKind.Enum, name, _enumTemplate.Render(new EnumSpec(namespaceName, name, MapFromAny(schema.Enum)))); + } + + private static IEnumerable MapFromAny(IEnumerable enumValues) => enumValues.SelectMany(v => + { + if (v is OpenApiString s) + { + return new[] + { + s.Value.SanitizeTypeName() + }; + } + + return Enumerable.Empty(); + }); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs b/src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs new file mode 100644 index 0000000..6e3a049 --- /dev/null +++ b/src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs @@ -0,0 +1,90 @@ +using Bismarck.CodeGenerator.Extensions; +using Bismarck.CodeGenerator.Models; + +using Microsoft.CodeAnalysis; +using Microsoft.OpenApi.Models; + +using Scriban; + +namespace Bismarck.CodeGenerator.Generators; + +public record PropertySpec(PropertyType Type, string Name) +{ + public PropertyType Type { get; } = Type; + + public string Name { get; } = Name; +} + +internal record ModelSpec(string Namespace, string TargetName) : SpecBase(Namespace, TargetName) +{ + public IList Properties { get; set; } = new List(); +} + +internal class ModelGenerator +{ + private readonly Template _modelTemplate; + private readonly EnumGenerator _enumGenerator; + + public ModelGenerator() + { + _modelTemplate = Template.Parse(typeof(ContractGenerator).LoadRelativeResource("templates", "SchemaModel.tmplcs")); + _enumGenerator = new EnumGenerator(); + } + + public void GenerateModelSchema(GeneratorContext context, IDictionary schemata, string namespaceName) + { + foreach ((string? key, OpenApiSchema? schema) in schemata) + { + var type = Type(schema, key); + var spec = new ModelSpec(namespaceName, key.SanitizeTypeName()); + GenerateProperties(context, spec, new[] + { + key + }, schema); + context.AddSource(type.Kind, spec.TargetName, _modelTemplate.Render(spec)); + } + } + + private void GenerateProperties(GeneratorContext context, ModelSpec modelSpec, IEnumerable location, OpenApiSchema schema) + { + var parentName = location.Last(); + foreach ((string? key, OpenApiSchema? value) in schema.Properties) + { + var propertyType = Type(value, parentName); + + switch (propertyType.Kind) + { + case SchemaKind.Simple: + modelSpec.Properties.Add(new PropertySpec(propertyType, key.SanitizeTypeName())); + break; + case SchemaKind.Enum: + _enumGenerator.Generate(context, value, modelSpec.Namespace, key.SanitizeTypeName()); + modelSpec.Properties.Add(new PropertySpec(propertyType, key.SanitizeTypeName())); + break; + } + } + } + + private static PropertyType Type(OpenApiSchema schema, string parentName) + { + return schema.Type switch + { + "object" => PropertyType.Object(parentName), + "array" => PropertyType.Array(parentName), + "string" when schema.Enum.Count == 0 => PropertyType.Simple(nameof(String)), + "string" when schema.Enum.Count > 0 => PropertyType.Enum(parentName), + "boolean" => PropertyType.Simple(nameof(Boolean)), + "integer" => schema.Format switch + { + "int64" => PropertyType.Simple(nameof(Int64)), + _ => PropertyType.Simple(nameof(Int32)) + }, + "number" => schema.Format switch + { + "float" => PropertyType.Simple(nameof(Single)), + _ => PropertyType.Simple(nameof(Double)) + }, + _ => PropertyType.Object(nameof(Object)) + }; + } +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Generators/SpecBase.cs b/src/Bismarck.CodeGenerator/Generators/SpecBase.cs new file mode 100644 index 0000000..ca583f8 --- /dev/null +++ b/src/Bismarck.CodeGenerator/Generators/SpecBase.cs @@ -0,0 +1,8 @@ +namespace Bismarck.CodeGenerator.Generators; + +public abstract record SpecBase(string Namespace, string TargetName) +{ + public string Namespace { get; } = Namespace; + + public string TargetName { get; } = TargetName; +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Models/GeneratorSpec.cs b/src/Bismarck.CodeGenerator/Models/GeneratorSpec.cs new file mode 100644 index 0000000..3d290d5 --- /dev/null +++ b/src/Bismarck.CodeGenerator/Models/GeneratorSpec.cs @@ -0,0 +1,10 @@ +using Microsoft.OpenApi.Models; + +namespace Bismarck.CodeGenerator.Models; + +internal record GeneratorSpec(OpenApiDocument ApiDocument, string ServiceName) +{ + public OpenApiDocument ApiDocument { get; } = ApiDocument; + + public string ServiceName { get; } = ServiceName; +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Models/Properties.cs b/src/Bismarck.CodeGenerator/Models/Properties.cs new file mode 100644 index 0000000..4d807e4 --- /dev/null +++ b/src/Bismarck.CodeGenerator/Models/Properties.cs @@ -0,0 +1,24 @@ +namespace Bismarck.CodeGenerator.Models; + +public enum SchemaKind +{ + Simple, + Object, + Array, + Enum +} + +public record PropertyType(string Name, SchemaKind Kind) +{ + public string Name { get; } = Name; + + public SchemaKind Kind { get; } = Kind; + + public static PropertyType Simple(string name) => new(name, SchemaKind.Simple); + + public static PropertyType Enum(string name) => new(name, SchemaKind.Enum); + + public static PropertyType Object(string name) => new(name, SchemaKind.Object); + + public static PropertyType Array(string name) => new($"IEnumerable<{name}>", SchemaKind.Array); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/templates/Enum.tmplcs b/src/Bismarck.CodeGenerator/templates/Enum.tmplcs new file mode 100644 index 0000000..cc51e38 --- /dev/null +++ b/src/Bismarck.CodeGenerator/templates/Enum.tmplcs @@ -0,0 +1,8 @@ +namespace {{ namespace }}; + +public enum {{ target_name }} +{ +{{- for value in values }} + {{ value -}}, +{{- end }} +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/templates/OperationInterface.tmplcs b/src/Bismarck.CodeGenerator/templates/OperationInterface.tmplcs new file mode 100644 index 0000000..5243f14 --- /dev/null +++ b/src/Bismarck.CodeGenerator/templates/OperationInterface.tmplcs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc; + +namespace {{ namespace }}; + +public interface I{{ operation_name }} +{ + Task On{{ operation_name }}Async(); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/templates/SchemaModel.tmplcs b/src/Bismarck.CodeGenerator/templates/SchemaModel.tmplcs new file mode 100644 index 0000000..361e078 --- /dev/null +++ b/src/Bismarck.CodeGenerator/templates/SchemaModel.tmplcs @@ -0,0 +1,8 @@ +namespace {{ namespace }}; + +public record {{ target_name }} +{ +{{- for property in properties }} + public {{ property.type.name }} {{ property.name }} { get; init; } +{{- end }} +} \ No newline at end of file