🔥 사용자 정의 타입 파싱하기

343자
4분

ExpressibleByArgument 프로토콜을 준수하는 모든 타입은 인자와 옵션으로 파싱할 수 있습니다. 표준 라이브러리에 있는 정수형, 부동소수점형, 문자열, 불리언 타입은 이미 ExpressibleByArgument프로토콜을 채택하고 있습니다.

개발자가 직접 만든 사용자 정의 타입도 ExpressibleByArgument를 준수하도록 만들 수 있습니다. init(argument:) 메서드를 구현하면 됩니다:

swift
struct Path: ExpressibleByArgument {
    var pathString: String
 
    // String 인자를 받아 Path 인스턴스를 초기화하는 생성자를 구현합니다.
    init?(argument: String) {
        self.pathString = argument
    }
}
 
struct Example: ParsableCommand {
    // Path 타입의 inputFile 인자를 선언합니다.
    @Argument var inputFile: Path
}
 
swift
struct Path: ExpressibleByArgument {
    var pathString: String
 
    // String 인자를 받아 Path 인스턴스를 초기화하는 생성자를 구현합니다.
    init?(argument: String) {
        self.pathString = argument
    }
}
 
struct Example: ParsableCommand {
    // Path 타입의 inputFile 인자를 선언합니다.
    @Argument var inputFile: Path
}
 

문자열 기반 열거형 같은 RawRepresentable 타입일 경우, 라이브러리에서 기본 구현을 제공하므로 개발자는 프로토콜 준수를 선언만 하면 됩니다.

swift
enum ReleaseMode: String, ExpressibleByArgument {
    case debug, release
}
 
struct Example: ParsableCommand {
    // ReleaseMode 타입의 mode 옵션을 선언합니다.
    @Option var mode: ReleaseMode
 
    mutating func run() throws {
        // 선택된 mode 값을 출력합니다.
        print(mode)
    }
}
 
swift
enum ReleaseMode: String, ExpressibleByArgument {
    case debug, release
}
 
struct Example: ParsableCommand {
    // ReleaseMode 타입의 mode 옵션을 선언합니다.
    @Option var mode: ReleaseMode
 
    mutating func run() throws {
        // 선택된 mode 값을 출력합니다.
        print(mode)
    }
}
 

사용자가 커맨드 라인에서 원시 값을 제공하면, 그 값을 개발자가 직접 만든 사용자 정의 타입으로 변환합니다. 이 과정에서 유효한 값만 허용하여 잘못된 입력을 방지할 수 있습니다:

text
% example --mode release
release
% example --mode future
Error: The value 'future' is invalid for '--mode <mode>'
text
% example --mode release
release
% example --mode future
Error: The value 'future' is invalid for '--mode <mode>'

만약 인자나 옵션으로 사용하고 싶은 타입이 ExpressibleByArgument 프로토콜을 채택하지 않는다면, 문자열을 해당 타입으로 변환하는 throwing 클로저인 transform을 제공하는 방법으로 사용할 수 있습니다. 이 방법은 RawRepresentable 프로토콜보다 구현이 복잡하거나, 직접 정의하지 않은 타입을 사용할 때 유용합니다.

swift
enum Format {
    case text
    case other(String)
 
    // String을 받아 Format 값을 반환하는 이니셜라이저를 구현합니다.
    // 잘못된 값이 주어지면 오류를 던집니다.
    init(_ string: String) throws {
        if string == "text" {
            self = .text
        } else {
            self = .other(string)
        }
    }
}
 
struct Example: ParsableCommand {
    // Format.init을 transform 함수로 지정하여 format 인자를 선언합니다.
    @Argument(transform: Format.init)
    var format: Format
}
 
swift
enum Format {
    case text
    case other(String)
 
    // String을 받아 Format 값을 반환하는 이니셜라이저를 구현합니다.
    // 잘못된 값이 주어지면 오류를 던집니다.
    init(_ string: String) throws {
        if string == "text" {
            self = .text
        } else {
            self = .other(string)
        }
    }
}
 
struct Example: ParsableCommand {
    // Format.init을 transform 함수로 지정하여 format 인자를 선언합니다.
    @Argument(transform: Format.init)
    var format: Format
}
 

사용자가 해당 타입에 유효하지 않은 값을 제공했다면 transform 함수에서 오류를 던져야 합니다. transform 함수 오류를 사용자 정의하는 방법은 사용자 정의 검증 제공하기를 참고하세요.

이렇게 ExpressibleByArgument 프로토콜과 transform 클로저를 활용하면, 개발자가 정의한 타입을 아주 유연하게 인자와 옵션으로 사용할 수 있습니다. 타입 유효성을 검사하고 파싱 과정을 제어할 수 있게 되죠. 여러분의 커맨드 라인 도구에 꼭 맞는 타입을 정의해 보세요. 사용자에게 더 직관적이고 안전한 인터페이스를 제공할 수 있습니다.

YouTube 영상

채널 보기
NestJS 커스텀 데코레이터 인자 전달 및 파이프 검증 활용법 | NestJS 가이드
NestJS 가드, 바이딩과 스코프 | NestJS 가이드
존 매카시가 들려주는 인공지능의 탄생 이야기
NestJS 커스텀 데코레이터, createParamDecorator 사용 | NestJS 가이드
API 응답 지연과 복잡한 에러, NestJS 인터셉터로 관리하는 방법 | NestJS 가이드
인터셉터와 RxJS로 캐시 시스템 구축하기 | NestJS 가이드
NestJS 인터셉터에서 map 연산자로 응답을 변환하는 방법 | NestJS 가이드
class-validator 와 DTO | NestJS 가이드