Replies: 3 comments 1 reply
-
Pardon my ignorance, but what is the use case for pattern matching on field types? |
Beta Was this translation helpful? Give feedback.
-
Pattern matching with a map is, i think "the Elixir way" to match parameters. How about splitting the parameters into required and not required params, where @doc """
...
### Argument binding
The doc should have a chapter appended created by the generator:
The request is sent using `GET` method using the path `/operation/:req_path_param`,
* Arguments
* `req_query_param`: **required**, sent by query `req-query-param`
* `req_cookie_param`: **required**, sent by cookie `REQ-cooKie-PARAM`
* `req_path_param`: **required, sent by parameter `:req-path-param` in path
* `req_defaulted_param`: optional, defaults to `"some default from spec"` sent via header `X-Req-Defaulted-Param`
* Options
* `some_optional_header_parameter`: sent via header `X-Some-Optional-Header-Parameter`
...
"""
@operation_defaults %{
req_defaulted_param: "some default from spec"
}
# this clause contains only required, but not defaulted params
def operation(%{
req_path_param: req_path_param,
req_query_param: req_query_param,
req_cookie_param: req_query_param
}=params, opts)
do
@operation_defaults
|>Map.merge(params)
|> operation(
opts
# This saves the info about which params have been defaulted
|> Keyword.put(:defaulted_params, Map.keys(@operation_defaults) -- Map.keys(params) )
)
end
# this clause pattern matches all required and all defaulted arguments
def operation(%{
req_defaulted_param: req_defaulted_param ,
req_path_param: req_path_param,
req_query_param: req_query_param,
req_cookie_param: req_query_param
},
opts
) do
# ... skipping the param transformation for clarity & going directly to `client.request(%{...})`
client.request(%{
# parameters is replacing the args field here
parameters : %{
# this is keyed by Processor.Operation.Param.location()
path: [req_path_param: req_path_param],
cookie: [ req_cookie_param: req_cookie_param ],
header: [req_defaulted_param: req_defaulted_param],
query: [req_query_param: req_query_param]
}
call: {Operations, :operation},
url: "/operation",
method: :get,
# :headers field should become part of the :parameters field (:header)
#headers: header_parameters,
response: [
#...
],
opts:
opts
|> Keyword.put(
:parameter_types, %{
req_path_param: {:string, :generic, spec_key: {:path, ":req-path-param"}, ...]},
req_defaulted_param: {:string, generic, spec_key: {:header, "X-Req-Defaulted-Param"}, default: "some default from spec"}
... # not expanding now on this
})
})
end What do you think abt. this?
Initially i wanted to expand on the code snippet, but i think that the snippet is more concise, than redoubling with words (and losing clarity 🤔). Are you open to a discussion about this? I think that it's only slightly off topic, as the way advanced type annotations should be sent is contingent on how to capture them and supply them in most useful format to the client. I have already written a dirty POC of a client with tesla using middlewares, but I'm leaning to writing a tesla client where each parameter location (as in spec) gets it's own middleware. I can publish and expand on this if there's interest. For my project, i needed a quick fix for header params, so i created this PR: That PR uses only the I'll leave it here, but I'll keep an eye and try to be responsive here if there's interest. |
Beta Was this translation helpful? Give feedback.
-
How about using the argument {:string, :discriminator, nullable: false, required: true}
#which is equivalent to
{:string, :discriminator, [nullable: false, required: true]} This could then be easily pattern matched in the client: def my_function({:string, :date, opts}), do: ...
def my_function({My.Module.Struct, :t, opts}), do: ... Options are then implementation details, which fall in the reponsibility of the clause matching on This should already be familiar from Config import Config
config :some_app,
key1: "value1",
key2: "value2"
import_config "#{config_env()}.exs" also from the app child spec: %{
id: Counter,
start: {Counter, :start_link, [0]}
} EDIT: accidentally deleted the prior comment on the same topic, so here we go again 😢 |
Beta Was this translation helpful? Give feedback.
-
I'm looking for feedback on top topic of how we represent field types in the generated code. Truthfully, I'm not satisfied with any of the proposals below, so ideas are very welcome!
Problem
Today, field types in the generated code are expressed using tuples:
This represents the primitive types available in OpenAPI and several of the common container types (array, union, etc.). It also makes nullability easy to pattern match. Unfortunately, it doesn't represent all of the data available in the spec:
This information could be useful for clients, especially when additional processing needs to take place (like with datetimes).
Proposals
I propose a breaking change to the way we represent field types in the generated schemas. I have the following goals in mind:
For easy pattern matching, maps are often helpful because the order of the keys does not matter:
However, we then have to consider whether the keys need to always be present (like a struct) or if we will sometimes have a type that is simply
%(type: :string}
. It isn't possible to match on the non-existence of keys, so it seems likely that every map will need the same keys — hence, a struct. But this could get cumbersome:Perhaps, then, some containers should remain tuples:
This brings up another option: keep using tuples like we are, but with an additional element that contains metadata as a map.
This would allow for pattern matching while keeping the code slightly more succinct, but still runs into the questions of whether we always put the same keys in the metadata.
Beta Was this translation helpful? Give feedback.
All reactions