cat swift/laboratorio-libreria-swift-pubblicabile.md
Laboratorio: libreria Swift pubblicabile
Laboratorio del Modulo 6: trasformare il dominio coupon in un package Swift con target, test, documentazione, CLI demo è CI.
Questo laboratorio chiude il modulo sul tooling professionale. L’obiettivo è creare una libreria Swift che non sia solo codice funzionante, ma un piccolo prodotto tecnico:
- package organizzato;
- target chiari;
- test;
- documentazione;
- demo CLI;
- comandi riproducibili;
- CI di base.
Obiettivo
Costruire CouponKit.
Struttura finale:
CouponKit/
Package.swift
README.md
Sources/
CouponCore/
CouponCLI/
Tests/
CouponCoreTests/
.github/
workflows/
swift.yml
Passo 1: creare package
mkdir CouponKit
cd CouponKit
swift package init --type library
Poi riorganizza:
Sources/CouponCore
Tests/CouponCoreTests
Aggiorna i nomi nel manifest.
Passo 2: manifest
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "CouponKit",
platforms: [
.macOS(.v14)
],
products: [
.library(name: "CouponCore", targets: ["CouponCore"]),
.executable(name: "coupon-cli", targets: ["CouponCLI"])
],
targets: [
.target(name: "CouponCore"),
.executableTarget(
name: "CouponCLI",
dependencies: ["CouponCore"]
),
.testTarget(
name: "CouponCoreTests",
dependencies: ["CouponCore"]
)
]
)
Passo 3: dominio pubblico minimo
In Sources/CouponCore:
public struct Coupon: Equatable, Sendable {
public let codice: String
public let sconto: Sconto
public let regole: [RegolaCoupon]
public init(codice: String, sconto: Sconto, regole: [RegolaCoupon]) {
self.codice = codice
self.sconto = sconto
self.regole = regole
}
}
Ricorda: in una libreria, public va dichiarato intenzionalmente. Non tutto deve essere pubblico.
Passo 4: engine
public struct CouponEngine: Sendable {
public init() {}
public func applica(_ coupon: Coupon, a carrello: Carrello) throws -> Carrello {
try valida(coupon, a: carrello)
return carrello.applicando(coupon.sconto)
}
}
La API deve leggere bene.
Passo 5: test
Con Swift Testing:
import Testing
@testable import CouponCore
@Test
func couponPercentualeRiduceTotale() throws {
let carrello = Carrello.fixture(totaleInCent: 10_000)
let coupon = Coupon.percentuale10
let aggiornato = try CouponEngine().applica(coupon, a: carrello)
#expect(aggiornato.totaleInCent == 9_000)
}
Crea fixture leggibili:
extension Coupon {
static let percentuale10 = Coupon(...)
}
Passo 6: CLI demo
In Sources/CouponCLI/main.swift:
import CouponCore
let engine = CouponEngine()
let carrello = Carrello.demo
let coupon = Coupon.demo
do {
let aggiornato = try engine.applica(coupon, a: carrello)
print("Totale: \(aggiornato.totaleInCent)")
} catch {
print("Coupon non valido: \(error)")
}
Esegui:
swift run coupon-cli
Passo 7: README
Un README utile contiene:
# CouponKit
Typed coupon modeling and validation for Swift packages.
## Install
Add this package with Swift Package Manager.
## Usage
```swift
let updated = try CouponEngine().applica(coupon, a: carrello)
Development
swift build
swift test
swift run coupon-cli
Il README non deve essere romanzo. Deve permettere di iniziare.
## Passo 8: DocC
Crea:
```text
Sources/CouponCore/CouponCore.docc/CouponCore.md
Con:
# CouponCore
Model coupon rules with strongly typed Swift APIs.
## Overview
Use `CouponEngine` to validate and apply coupons to carts.
Poi documenta i simboli pubblici principali con ///.
Passo 9: CI
name: Swift
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Build
run: swift build
- name: Test
run: swift test
Passo 10: checklist release
Prima di considerare il package pubblicabile:
swift buildpassa;swift testpassa;swift run coupon-clifunziona;- README spiega installazione e uso;
- DocC esiste;
- API pubbliche hanno commenti;
- errori pubblici sono documentati;
- CI è configurata;
- nessuna dipendenza inutile nel core;
- i nomi dei target sono chiari.
Esercizi
Base · facile
Scrivi un Package.swift con un target libreria e un target di test, e verifica che swift build e swift test passino.
Soluzione
// swift-tools-version:6.0
import PackageDescription
let package = Package(
name: "CouponKit",
products: [.library(name: "CouponKit", targets: ["CouponKit"])],
targets: [
.target(name: "CouponKit"),
.testTarget(name: "CouponKitTests", dependencies: ["CouponKit"]),
]
)
swift build # compila la libreria
swift test # esegue i test del testTarget
Il manifest dichiara cosa il package produce (products) e i targets che lo compongono. Il testTarget dipende dalla libreria per poterla testare.
Intermedio · medio
Aggiungi un target eseguibile per una CLI demo che usa la libreria, separato dal core. Spiega perché conviene tenere CLI e libreria in target distinti.
Soluzione
targets: [
.target(name: "CouponKit"), // core riusabile
.executableTarget(name: "coupon-cli", dependencies: ["CouponKit"]), // demo
.testTarget(name: "CouponKitTests", dependencies: ["CouponKit"]),
]
swift run coupon-cli --codice SCONTO10 --totale 50
Separare i target tiene il core libreria privo di codice da riga di comando (parsing argomenti, stampa): chi importa CouponKit in un’app non si porta dietro la CLI né le sue dipendenze. La CLI diventa un semplice consumatore della libreria, utile anche come esempio d’uso vivo.
Sfida · difficile
Documenta le API pubbliche con DocC e configura una CI di base che esegue build, test e documentazione a ogni push. Spiega cosa rende un package “un piccolo prodotto” e non solo codice funzionante.
Soluzione
/// Applica un coupon a un totale.
/// - Parameters:
/// - codice: il codice del coupon (non vuoto).
/// - totale: importo positivo del carrello.
/// - Returns: l'esito con totale finale ed eventuali errori.
/// - Throws: ``ErroreCoupon`` se il codice o il totale non sono validi.
public func applica(codice: String, a totale: Double) throws -> EsitoCoupon { /* ... */ }
# .github/workflows/ci.yml
on: [push, pull_request]
jobs:
build-test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: swift build
- run: swift test
Un package è “un prodotto” quando un’altra persona può adottarlo senza chiederti niente: API pubbliche documentate (DocC), README con installazione e uso, errori pubblici spiegati, e una CI che garantisce che build e test passino a ogni modifica. Il codice che “gira sulla mia macchina” diventa software affidabile quando è documentato, testato in automatico e riproducibile.
Estensioni
Per rendere il package più professionale:
- aggiungi semantic versioning;
- aggiungi changelog;
- aggiungi badge CI nel README;
- aggiungi esempi DocC;
- aggiungi test Linux in matrix;
- aggiungi benchmark se le performance contano;
- separa
CouponHTTPcome target futuro.