/* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using GlobExpressions;
namespace ApiGenerator.Configuration
{
public static class CodeConfiguration
{
private static readonly Glob[] OperationsToInclude =
{
// e.g. new Glob("nodes.*"),
};
public static bool IncludeOperation(string name) => OperationsToInclude.Any(g => g.IsMatch(name));
///
/// Map API default names for API's we are only supporting on the low level client first
///
private static readonly Dictionary LowLevelApiNameMapping = new Dictionary
{
{ "indices.delete_index_template", "DeleteIndexTemplateV2" },
{ "indices.get_index_template", "GetIndexTemplateV2" },
{ "indices.put_index_template", "PutIndexTemplateV2" }
};
///
/// Scan all OSC source code files for Requests and look for the [MapsApi(filename)] attribute.
/// The class name minus Request is used as the canonical .NET name for the API.
///
public static readonly Dictionary HighLevelApiNameMapping =
(from f in new DirectoryInfo(GeneratorLocations.OpenSearchClientFolder).GetFiles("*.cs", SearchOption.AllDirectories)
let contents = File.ReadAllText(f.FullName)
let c = Regex.Replace(contents, @"^.+\[MapsApi\(""([^ \r\n]+)""\)\].*$", "$1", RegexOptions.Singleline)
where !c.Contains(" ") //filter results that did not match
select new { Value = f.Name.Replace("Request", ""), Key = c.Replace(".json", "") })
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value.Replace(".cs", ""));
public static readonly HashSet EnableHighLevelCodeGen = new HashSet();
public static bool IsNewHighLevelApi(string apiFileName) =>
// no requests with [MapsApi("filename.json")] found
!HighLevelApiNameMapping.ContainsKey(apiFileName.Replace(".json", ""));
public static bool IgnoreHighLevelApi(string apiFileName)
{
//always generate already mapped requests
if (HighLevelApiNameMapping.ContainsKey(apiFileName.Replace(".json", ""))) return false;
return !EnableHighLevelCodeGen.Contains(apiFileName);
}
private static Dictionary _apiNameMapping;
public static Dictionary ApiNameMapping
{
get
{
if (_apiNameMapping != null) return _apiNameMapping;
lock (LowLevelApiNameMapping)
{
if (_apiNameMapping == null)
{
var mapping = new Dictionary(HighLevelApiNameMapping);
foreach (var (k, v) in LowLevelApiNameMapping)
mapping[k] = v;
_apiNameMapping = mapping;
}
return _apiNameMapping;
}
}
}
private static readonly string ResponseBuilderAttributeRegex = @"^.+\[ResponseBuilderWithGeneric\(""([^ \r\n]+)""\)\].*$";
///
/// Scan all OSC source code files for Requests and look for the [MapsApi(filename)] attribute.
/// The class name minus Request is used as the canonical .NET name for the API.
///
public static readonly Dictionary ResponseBuilderInClientCalls =
(from f in new DirectoryInfo(GeneratorLocations.OpenSearchClientFolder).GetFiles("*.cs", SearchOption.AllDirectories)
from l in File.ReadLines(f.FullName)
where Regex.IsMatch(l, ResponseBuilderAttributeRegex)
let c = Regex.Replace(l, @"^.+\[ResponseBuilderWithGeneric\(""([^ \r\n]+)""\)\].*$", "$1", RegexOptions.Singleline)
select new { Key = f.Name.Replace(".cs", ""), Value = c })
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value);
public static readonly Dictionary DescriptorGenericsLookup =
(from f in new DirectoryInfo(GeneratorLocations.OpenSearchClientFolder).GetFiles("*Request.cs", SearchOption.AllDirectories)
let name = Path.GetFileNameWithoutExtension(f.Name).Replace("Request", "")
let contents = File.ReadAllText(f.FullName)
let c = Regex.Replace(contents, $@"^.+class ({name}Descriptor(?:<[^>\r\n]+>)?[^ \r\n]*).*$", "$1", RegexOptions.Singleline)
let key = $"{name}Descriptor"
select new { Key = key, Value = Regex.Replace(c, @"^.*?(?:(\<.+>).*?)?$", "$1") })
.DistinctBy(v => v.Key)
.OrderBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value);
/// Scan all OSC files for request interfaces and note any generics declared on them
private static readonly List> AllKnownRequestInterfaces = (
// find all files in OSC ending with Request.cs
from f in new DirectoryInfo(GeneratorLocations.OpenSearchClientFolder).GetFiles("*Request.cs", SearchOption.AllDirectories)
from l in File.ReadLines(f.FullName)
// attempt to locate all Request interfaces lines
where Regex.IsMatch(l, @"^.+interface [^ \r\n]+Request")
//grab the interface name including any generics declared on it
let c = Regex.Replace(l, @"^.+interface ([^ \r\n]+Request(?:<[^>\r\n]+>)?[^ \r\n]*).*$", "$1", RegexOptions.Singleline)
where c.StartsWith("I") && c.Contains("Request")
let request = Regex.Replace(c, "<.*$", "")
let generics = Regex.Replace(c, @"^.*?(?:(\<.+>).*?)?$", "$1")
select Tuple.Create(request, generics)
)
.OrderBy(v=>v.Item1)
.ToList();
public static readonly HashSet GenericOnlyInterfaces = new HashSet(AllKnownRequestInterfaces
.GroupBy(v => v.Item1)
.Where(g => g.All(v => !string.IsNullOrEmpty(v.Item2)))
.Select(g => g.Key)
.ToList());
public static readonly HashSet DocumentRequests = new HashSet((
// find all files in OSC ending with Request.cs
from f in new DirectoryInfo(GeneratorLocations.OpenSearchClientFolder).GetFiles("*Request.cs", SearchOption.AllDirectories)
from l in File.ReadLines(f.FullName)
// attempt to locate all Request interfaces lines
where Regex.IsMatch(l, @"^.+interface [^ \r\n]+Request")
where l.Contains("IDocumentRequest")
let c = Regex.Replace(l, @"^.+interface ([^ \r\n]+Request(?:<[^>\r\n]+>)?[^ \r\n]*).*$", "$1", RegexOptions.Singleline)
//grab the interface name including any generics declared on it
let request = Regex.Replace(c, "<.*$", "")
select request
)
.ToList());
public static readonly Dictionary DescriptorConstructors = (
// find all files in OSC ending with Request.cs
from f in new DirectoryInfo(GeneratorLocations.OpenSearchClientFolder).GetFiles("*Request.cs", SearchOption.AllDirectories)
let descriptor = Path.GetFileNameWithoutExtension(f.Name).Replace("Request", "Descriptor")
let re = $@"^.+public {descriptor}\(([^\r\n\)]+?)\).*$"
from l in File.ReadLines(f.FullName)
where Regex.IsMatch(l, re)
let args = Regex.Replace(l, re, "$1", RegexOptions.Singleline)
where !string.IsNullOrWhiteSpace(args) && !args.Contains(": base")
select (Descriptor: descriptor, Args: args)
)
.ToDictionary(r => r.Descriptor, r => r.Args);
public static readonly Dictionary RequestInterfaceGenericsLookup =
AllKnownRequestInterfaces
.GroupBy(v=>v.Item1)
.Select(g=>g.Last())
.ToDictionary(k => k.Item1, v => v.Item2);
///
/// Some API's reuse response this is a hardcoded map of these cases
///
private static Dictionary ResponseReroute = new Dictionary
{
{"UpdateByQueryRethrottleResponse", ("ListTasksResponse", "")},
{"DeleteByQueryRethrottleResponse", ("ListTasksResponse", "")},
{"MultiSearchTemplateResponse", ("MultiSearchResponse", "")},
{"ScrollResponse", ("SearchResponse", "")},
{"SearchTemplateResponse", ("SearchResponse", "")},
};
/// Create a dictionary lookup of all responses and their generics
public static readonly SortedDictionary ResponseLookup = new SortedDictionary(
(
// find all files in OSC ending with Request.cs
from f in new DirectoryInfo(GeneratorLocations.OpenSearchClientFolder).GetFiles("*Response.cs", SearchOption.AllDirectories)
from l in File.ReadLines(f.FullName)
// attempt to locate all Response class lines
where Regex.IsMatch(l, @"^.+public class [^ \r\n]+Response")
//grab the response name including any generics declared on it
let c = Regex.Replace(l, @"^.+public class ([^ \r\n]+Response(?:<[^>\r\n]+>)?[^ \r\n]*).*$", "$1", RegexOptions.Singleline)
where c.Contains("Response")
let response = Regex.Replace(c, "<.*$", "")
let generics = Regex.Replace(c, @"^.*?(?:(\<.+>).*?)?$", "$1")
select (response, (response, generics))
)
.Concat(ResponseReroute.Select(kv=>(kv.Key, (kv.Value.Item1, kv.Value.Item2))))
.ToDictionary(t=>t.Item1, t=>t.Item2));
}
}