Skip to content

Commit 380eb72

Browse files
authored
Don't error when decoding optional unparsed values (#286)
We're newly able to decode actual optional types due to allowing unparsed variable properties. "Normal" optional values are still wrapped in non-optional property wrappers, so ArgumentDecoder didn't need to handle optional values until now. Fixes #285
1 parent 4793b0f commit 380eb72

File tree

3 files changed

+121
-42
lines changed

3 files changed

+121
-42
lines changed

Sources/ArgumentParser/Parsing/ArgumentDecoder.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,19 @@ final class ParsedArgumentsContainer<K>: KeyedDecodingContainerProtocol where K
112112
return try type.init(from: subDecoder)
113113
}
114114

115+
func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? where T : Decodable {
116+
let subDecoder = SingleValueDecoder(userInfo: decoder.userInfo, underlying: decoder, codingPath: codingPath + [key], key: InputKey(key), parsedElement: element(forKey: key))
117+
do {
118+
return try type.init(from: subDecoder)
119+
} catch let error as ParserError {
120+
if case .noValue = error {
121+
return nil
122+
} else {
123+
throw error
124+
}
125+
}
126+
}
127+
115128
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
116129
fatalError()
117130
}

Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -115,45 +115,3 @@ extension SimpleEndToEndTests {
115115
XCTAssertThrowsError(try Baz.parse(["--name", "--format", "Bar", "Foo"]))
116116
}
117117
}
118-
119-
// MARK: Two values + unparsed variable
120-
121-
fileprivate struct Qux: ParsableArguments {
122-
@Option() var name: String
123-
@Flag() var verbose = false
124-
var count = 0
125-
}
126-
127-
fileprivate struct Quizzo: ParsableArguments {
128-
@Option() var name: String
129-
@Flag() var verbose = false
130-
let count = 0
131-
}
132-
133-
extension SimpleEndToEndTests {
134-
func testParsing_TwoPlusUnparsed() throws {
135-
AssertParse(Qux.self, ["--name", "Qux"]) { qux in
136-
XCTAssertEqual(qux.name, "Qux")
137-
XCTAssertFalse(qux.verbose)
138-
XCTAssertEqual(qux.count, 0)
139-
}
140-
AssertParse(Qux.self, ["--name", "Qux", "--verbose"]) { qux in
141-
XCTAssertEqual(qux.name, "Qux")
142-
XCTAssertTrue(qux.verbose)
143-
XCTAssertEqual(qux.count, 0)
144-
}
145-
146-
AssertParse(Quizzo.self, ["--name", "Qux", "--verbose"]) { quizzo in
147-
XCTAssertEqual(quizzo.name, "Qux")
148-
XCTAssertTrue(quizzo.verbose)
149-
XCTAssertEqual(quizzo.count, 0)
150-
}
151-
}
152-
153-
func testParsing_TwoPlusUnparsed_Fails() throws {
154-
XCTAssertThrowsError(try Qux.parse([]))
155-
XCTAssertThrowsError(try Qux.parse(["--name"]))
156-
XCTAssertThrowsError(try Qux.parse(["--name", "Qux", "--count"]))
157-
XCTAssertThrowsError(try Qux.parse(["--name", "Qux", "--count", "2"]))
158-
}
159-
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//===----------------------------------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift Argument Parser open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import XCTest
13+
import ArgumentParserTestHelpers
14+
import ArgumentParser
15+
16+
final class UnparsedValuesEndToEndTests: XCTestCase {}
17+
18+
// MARK: Two values + unparsed variable
19+
20+
fileprivate struct Qux: ParsableArguments {
21+
@Option() var name: String
22+
@Flag() var verbose = false
23+
var count = 0
24+
}
25+
26+
fileprivate struct Quizzo: ParsableArguments {
27+
@Option() var name: String
28+
@Flag() var verbose = false
29+
let count = 0
30+
}
31+
32+
extension UnparsedValuesEndToEndTests {
33+
func testParsing_TwoPlusUnparsed() throws {
34+
AssertParse(Qux.self, ["--name", "Qux"]) { qux in
35+
XCTAssertEqual(qux.name, "Qux")
36+
XCTAssertFalse(qux.verbose)
37+
XCTAssertEqual(qux.count, 0)
38+
}
39+
AssertParse(Qux.self, ["--name", "Qux", "--verbose"]) { qux in
40+
XCTAssertEqual(qux.name, "Qux")
41+
XCTAssertTrue(qux.verbose)
42+
XCTAssertEqual(qux.count, 0)
43+
}
44+
45+
AssertParse(Quizzo.self, ["--name", "Qux", "--verbose"]) { quizzo in
46+
XCTAssertEqual(quizzo.name, "Qux")
47+
XCTAssertTrue(quizzo.verbose)
48+
XCTAssertEqual(quizzo.count, 0)
49+
}
50+
}
51+
52+
func testParsing_TwoPlusUnparsed_Fails() throws {
53+
XCTAssertThrowsError(try Qux.parse([]))
54+
XCTAssertThrowsError(try Qux.parse(["--name"]))
55+
XCTAssertThrowsError(try Qux.parse(["--name", "Qux", "--count"]))
56+
XCTAssertThrowsError(try Qux.parse(["--name", "Qux", "--count", "2"]))
57+
}
58+
}
59+
60+
// MARK: Nested unparsed decodable type
61+
62+
63+
fileprivate struct Foo: ParsableCommand {
64+
@Flag var foo: Bool = false
65+
var config: Config?
66+
@OptionGroup var opt: OptionalArguments
67+
@OptionGroup var def: DefaultedArguments
68+
}
69+
70+
fileprivate struct Config: Decodable {
71+
var name: String
72+
var age: Int
73+
}
74+
75+
fileprivate struct OptionalArguments: ParsableArguments {
76+
@Argument var title: String?
77+
@Option var edition: Int?
78+
}
79+
80+
fileprivate struct DefaultedArguments: ParsableArguments {
81+
@Option var one = 1
82+
@Option var two = 2
83+
}
84+
85+
extension UnparsedValuesEndToEndTests {
86+
func testUnparsedNestedValues() {
87+
AssertParse(Foo.self, []) { foo in
88+
XCTAssertFalse(foo.foo)
89+
XCTAssertNil(foo.opt.title)
90+
XCTAssertNil(foo.opt.edition)
91+
XCTAssertEqual(1, foo.def.one)
92+
XCTAssertEqual(2, foo.def.two)
93+
}
94+
95+
AssertParse(Foo.self, ["--foo", "--edition", "5", "Hello", "--one", "2", "--two", "1"]) { foo in
96+
XCTAssertTrue(foo.foo)
97+
XCTAssertEqual("Hello", foo.opt.title)
98+
XCTAssertEqual(5, foo.opt.edition)
99+
XCTAssertEqual(2, foo.def.one)
100+
XCTAssertEqual(1, foo.def.two)
101+
}
102+
}
103+
104+
func testUnparsedNestedValues_Fails() {
105+
XCTAssertThrowsError(try Foo.parse(["--edition", "aaa"]))
106+
XCTAssertThrowsError(try Foo.parse(["--one", "aaa"]))
107+
}
108+
}

0 commit comments

Comments
 (0)