New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(amplify-xcode): generate JSON schema #1080
Conversation
Codecov Report
@@ Coverage Diff @@
## main #1080 +/- ##
==========================================
+ Coverage 57.98% 58.53% +0.54%
==========================================
Files 519 650 +131
Lines 14888 19094 +4206
==========================================
+ Hits 8633 11176 +2543
- Misses 6255 7918 +1663
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
General question: Is there an opportunity to use an existing schema definition such as JSON Schema, Swagger, or Smithy? These are generally for service-side APIs, and I haven't investigated their applicability for a CLI use case, but it would be great if we could re-use prior art rather than creating a new standard. :D |
case parameters | ||
} | ||
|
||
protocol CLICommandEncodable: Encodable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an existing CLICommand
that it would make more sense to add Encodable
conformance to? If not, "Encodable" in the protocol name seems unnecessary, unless it's the reason for the protocol's existence, as in marker protocols or something where the protocol is tightly bound to the behavior the protocol describes--see discussion at https://swift.org/documentation/api-design-guidelines/#naming
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's no existing CLICommand
, the only reason for the CLICommandEncodable
's existence is to have Encodable
conformance -- but I'm thinking that would make sense to just have a generic CLICommand
protocol and let it conform to Encodable
. Thoughts?
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/CLICommandEncodable.swift
Outdated
Show resolved
Hide resolved
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/CLICommandEncodableParameter.swift
Outdated
Show resolved
Hide resolved
import Foundation | ||
|
||
/// Encodable representation of CLI parameter. | ||
enum CLICommandEncodableParameter: Hashable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, would it make more sense to add Encodable
performance to an existing CLICommandParameter
protocol or concrete type, rather than creating a new type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately there's no existing CLICommandParameter
type as the parameters are being handled by the ArgumentParser library via property wrappers (@Option
, @Flag
) -- the only reason for having an extra type is to retain information about commands' parameters and use it for generating the schema
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/CLICommandEncodableParameter.swift
Outdated
Show resolved
Hide resolved
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/CLICommandGenerateJSONSchema.swift
Outdated
Show resolved
Hide resolved
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/CLICommandEncodable.swift
Outdated
Show resolved
Hide resolved
var commands: [AnyCLICommandEncodable] = [] | ||
|
||
init() { | ||
for command in AmplifyXcode.configuration.subcommands where command != CLICommandGenerateJSONSchema.self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we generating a schema for the "Generate" command?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated, generate-schema
command generates a schema of itself :)
protocol CLICommandEncodable: Encodable { | ||
static var commandName: String { get } | ||
static var abstract: String { get } | ||
static var paramsRegistry: CLICommandEncodableParametersRegistry { get } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need an additional wrapping type here, or can we just make params
a Set
with direct get
access?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually don't need it, I just wanted to add a layer of abstraction and hide the underlying storage, Set
. (I don't even really like the name :P )
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/Support/ArgumentParser+CLICommandEncodable.swift
Outdated
Show resolved
Hide resolved
Co-authored-by: Tim Schmelter <schmelte@amazon.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved w/comments
extension Option where Value: ExpressibleByArgument { | ||
init(wrappedValue: Value, name: String, help: String, _ registry: CLICommandEncodableParametersRegistry) { | ||
init(wrappedValue: Value, name: String, help: String, _ parameters: inout Set<CLICommandParameter>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor naming suggestion to clarify the intent of the parameters
and why it's an inout
:
init(wrappedValue: Value, name: String, help: String, _ parameters: inout Set<CLICommandParameter>) { | |
init(wrappedValue: Value, name: String, help: String, updating parameters: inout Set<CLICommandParameter>) { |
Result at the call site would look like:
@Option(name: "path", help: "Project base path", updating: ¶meters)
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call, thanks!
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/CLICommandImportConfig.swift
Outdated
Show resolved
Hide resolved
AmplifyTools/AmplifyXcode/Sources/AmplifyXcode/CLICommandImportModels.swift
Outdated
Show resolved
Hide resolved
/// and annotated with `@propertyWrapper`s `@Option`, `@Flag` and `@Argument` provided by `ArgumentParser`. | ||
/// `ArgumentParser` derives parameters names from property names (i.e., an`outputPath` option becomes `--output-path`) | ||
/// making thus impossible to reliably generate a JSON representation of a command and its parameters. | ||
/// Therefore we use the following enum to keep track of each parameter and their attributes (name, type and help text). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
} | ||
|
||
init(name: String, help: String, _ registry: CLICommandEncodableParametersRegistry) { | ||
init(name: String, help: String, _ parameters: inout Set<CLICommandParameter>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init(name: String, help: String, _ parameters: inout Set<CLICommandParameter>) { | |
init(name: String, help: String, updating parameters: inout Set<CLICommandParameter>) { |
} | ||
} | ||
|
||
extension Flag where Value == Bool { | ||
init(wrappedValue: Value, name: String, help: String, _ registry: CLICommandEncodableParametersRegistry) { | ||
init(wrappedValue: Value, name: String, help: String, _ parameters: inout Set<CLICommandParameter>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init(wrappedValue: Value, name: String, help: String, _ parameters: inout Set<CLICommandParameter>) { | |
init(wrappedValue: Value, name: String, help: String, updating parameters: inout Set<CLICommandParameter>) { |
Description of changes:
This PR introduces a new
amplify-xcode genenerate-schema
command to generate a JSON representation of the CLI and its commands as an intermediate step to further generate necessary JavaScript/TypeScript bindings.In order to properly generate bindings and documentation for each commands, an interface describing parameters and their usage is generated according to the following specs:
Based on the current set of commands defined in
amplify-xcode
, the generated JSON schema will look likeThe following implementation is the results of a set of constraints/limitations in the ArgumentParser library and Swift runtime.
Using reflection to inspect a command type and generate a schema isn't a viable option due to the following constraints:
@Option
@Flag
property wrappers by using a privateString
extension, so for example a property namedfileOutputPath
will be converted to afile-output-path
parameter when used to invoke the CLI@Option
and@Flag
are generics on their wrapped value, that makes casting and/or infer their types using reflection almost impossibleArgumentParser
to parse value and/or generate help messages are private to the library and not exposed to its consumersCheck points:
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.