forked from googleapis/elixir-google-api
-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement generator in Elixir (googleapis#1327)
* Use discovery api * Fix Tesla usage * Fix tesla error handling * Put the tesla dependency back * Add stub Elixir generator * Add basic generator test for reading all schemas * output files to directory * Fill out some descriptions * Use token pattern * Get the right model properties * lint * Add elixir template * Stub for generating API modules * Build apis * Refactor to split generator models * Refactor Type * Refactor types * Fix date type * Mix format * Fix license headers * Refactor Token * Add tests for parsing parameters * Fix param types * Fix path parameters * Handle global optional params * Formatting * Cleanup and docs * Type test * Add model test * Refactor model to pass tests * Cleanup * Add test for loading nested unnamed schemas * Fix test for nested model names * We're ok fixing the naming scheme for nested models * Fix style * Generate all apis, fix typespec for endpoint with no params * Fix variable name in apis * Fix collection of nested methods * Handle no return type for methods * Typespec for no return type endpoint should be 'nil' * Fix list types for parameters * Add request body param * Add test for no parameters * Handle overwriting the request parameter name * Pass ResourceContext when parsing parameters * ResourceContext can now track the base_path and handles path generation * Endpoint.from_discovery_method now returns a list of Endpoints * Handle supportsMediaUpload * Write the connection.ex file. Fix baseUrl for media upload clients * Fix upload type and return type * Fix indentation * Fix double slash in resource paths * Ensure leading directories exist * Fix map types * Handle google-datetime types * Handle nested array/map types * Fix nested model names * Fix handling Date class * Fix only URI encode string params in paths * Fix uri escaping * Handle dataWrapped apis * Handle resources with no methods * Make all license headers 2019 * Fix copyright header to use Google LLC
- Loading branch information
Showing
24 changed files
with
2,492 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
# Copyright 2019 Google Inc. | ||
# | ||
# Licensed 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. | ||
|
||
defmodule GoogleApis.Generator.ElixirGenerator do | ||
@moduledoc """ | ||
Code generator written in Elixir which takes a Google Discovery document and | ||
generates an Elixir client library. | ||
""" | ||
|
||
@behaviour GoogleApis.Generator | ||
alias GoogleApis.ApiConfig | ||
alias GoogleApi.Discovery.V1.Model.{JsonSchema, RestDescription} | ||
|
||
alias GoogleApis.Generator.ElixirGenerator.{ | ||
Api, | ||
Endpoint, | ||
Model, | ||
Parameter, | ||
Renderer, | ||
ResourceContext, | ||
Token | ||
} | ||
|
||
@doc """ | ||
Run the generator for the specified api configuration | ||
""" | ||
@spec generate_client(ApiConfig.t()) :: {:ok, any()} | {:error, String.t()} | ||
def generate_client(api_config) do | ||
Token.build(api_config) | ||
|> load_models | ||
|> update_model_properties | ||
|> create_directories | ||
|> write_model_files | ||
|> load_global_optional_params | ||
|> load_apis | ||
|> write_api_files | ||
|> write_connection | ||
end | ||
|
||
defp load_models(token) do | ||
models = all_models(token.rest_description) | ||
|
||
token | ||
|> Map.put(:models, models) | ||
|> Map.put( | ||
:models_by_name, | ||
Enum.reduce(models, %{}, fn model, acc -> Map.put(acc, model.name, model) end) | ||
) | ||
end | ||
|
||
defp update_model_properties(token) do | ||
Map.update!(token, :models, fn models -> | ||
models | ||
|> Enum.map(fn model -> | ||
Model.update_properties(model, token.resource_context) | ||
end) | ||
end) | ||
end | ||
|
||
defp create_directories(token) do | ||
IO.puts("Creating leading directories") | ||
File.mkdir_p!(Path.join(token.base_dir, "api")) | ||
File.mkdir_p!(Path.join(token.base_dir, "model")) | ||
token | ||
end | ||
|
||
defp write_connection(token) do | ||
scopes = token.rest_description.auth.oauth2.scopes | ||
otp_app = "google_api_#{Macro.underscore(token.rest_description.name)}" | ||
|
||
path = Path.join(token.base_dir, "connection.ex") | ||
IO.puts("Writing connection.ex.") | ||
|
||
File.write!( | ||
path, | ||
Renderer.connection(token.namespace, scopes, otp_app, token.base_url) | ||
) | ||
end | ||
|
||
defp write_model_files(%{models: models, namespace: namespace, base_dir: base_dir} = token) do | ||
models | ||
|> Enum.each(fn model -> | ||
path = Path.join([base_dir, "model", Model.filename(model)]) | ||
IO.puts("Writing #{model.name} to #{path}.") | ||
|
||
File.write!( | ||
path, | ||
Renderer.model(model, namespace) | ||
) | ||
end) | ||
|
||
token | ||
end | ||
|
||
defp load_global_optional_params(token) do | ||
params = token.rest_description.parameters || [] | ||
|
||
global_optional_parameters = | ||
params | ||
|> Enum.map(fn {name, schema} -> | ||
Parameter.from_json_schema(name, schema, token.resource_context) | ||
end) | ||
|> Enum.sort_by(fn param -> param.name end) | ||
|
||
Map.put(token, :global_optional_parameters, global_optional_parameters) | ||
end | ||
|
||
defp load_apis(token) do | ||
Map.put(token, :apis, all_apis(token.rest_description, token.resource_context)) | ||
end | ||
|
||
defp write_api_files(token) do | ||
token.apis | ||
|> Enum.each(fn api -> | ||
path = Path.join([token.base_dir, "api", Api.filename(api)]) | ||
IO.puts("Writing #{api.name} to #{path}.") | ||
|
||
File.write!( | ||
path, | ||
Renderer.api(api, token.namespace, token.global_optional_parameters, token.data_wrapped) | ||
) | ||
end) | ||
|
||
token | ||
end | ||
|
||
@doc """ | ||
Returns all Apis found from the provided RestDescription | ||
""" | ||
@spec all_apis(RestDescription.t()) :: list(Api.t()) | ||
def all_apis(rest_description) do | ||
all_apis(rest_description, ResourceContext.default()) | ||
end | ||
|
||
@doc """ | ||
Returns all Apis found from the provided RestDescription and ResourceContext | ||
""" | ||
@spec all_apis(RestDescription.t(), ResourceContext.t()) :: list(Api.t()) | ||
def all_apis(%{resources: resources}, context) do | ||
resources | ||
|> Enum.map(fn {name, resource} -> | ||
name = Macro.camelize(name) | ||
methods = collect_methods(resource) | ||
|
||
%Api{ | ||
name: name, | ||
description: "API calls for all endpoints tagged `#{name}`.", | ||
endpoints: | ||
Enum.flat_map(methods, fn {_, method} -> | ||
Endpoint.from_discovery_method(method, context) | ||
end) | ||
} | ||
end) | ||
end | ||
|
||
defp collect_methods(%{resources: resources, methods: methods}) do | ||
collect_methods_from_methods(methods) ++ collect_methods_from_resources(resources) | ||
end | ||
|
||
defp collect_methods_from_methods(nil), do: [] | ||
defp collect_methods_from_methods(methods), do: Enum.into(methods, []) | ||
|
||
defp collect_methods_from_resources(nil), do: [] | ||
|
||
defp collect_methods_from_resources(resources) do | ||
Enum.flat_map(resources, fn {name, resource} -> | ||
collect_methods(resource) | ||
end) | ||
end | ||
|
||
@doc """ | ||
Returns all Models found from the provided RestDescription | ||
""" | ||
@spec all_models(RestDescription.t()) :: list(Model.t()) | ||
def all_models(rest_description) do | ||
Model.from_schemas(rest_description.schemas) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Copyright 2019 Google Inc. | ||
# | ||
# Licensed 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. | ||
|
||
defmodule GoogleApis.Generator.ElixirGenerator.Api do | ||
@moduledoc """ | ||
An Api represents a collection of endpoints. | ||
""" | ||
|
||
@type t :: %__MODULE__{ | ||
:name => String.t(), | ||
:description => String.t(), | ||
:endpoints => list(Endpoint.t()) | ||
} | ||
|
||
defstruct [:name, :description, :endpoints] | ||
|
||
@doc """ | ||
Returns the name of the file that should be generated. | ||
""" | ||
@spec filename(t) :: String.t() | ||
def filename(api) do | ||
"#{Macro.underscore(api.name)}.ex" | ||
end | ||
end |
Oops, something went wrong.