From bc635ecefead678d236e6a85f7e1411c087b4561 Mon Sep 17 00:00:00 2001 From: Anand Date: Sun, 7 Jun 2026 10:07:03 +0530 Subject: [PATCH 1/2] Prepare 1.0.0: OSS files, license headers, Sendable fix on NexioConfig --- .github/ISSUE_TEMPLATE/bug_report.md | 21 ++++---- .github/ISSUE_TEMPLATE/feature_request.md | 16 +++--- .github/PULL_REQUEST_TEMPLATE.md | 17 +++++++ CHANGELOG.md | 29 +++++++++++ CODE_OF_CONDUCT.md | 26 ++++++++++ CONTRIBUTING.md | 50 +++++++++++++++++++ SECURITY.md | 21 ++++++++ Sources/Nexio/Auth/AuthInterceptor.swift | 5 +- Sources/Nexio/Auth/AuthStrategy.swift | 5 +- Sources/Nexio/Core/Endpoint.swift | 5 +- Sources/Nexio/Core/NexioClient.swift | 5 +- Sources/Nexio/Core/NexioConfig.swift | 12 ++++- Sources/Nexio/Core/NexioRequest.swift | 5 +- Sources/Nexio/Errors/NexioError.swift | 5 +- Sources/Nexio/Image/ImageLoader.swift | 5 +- Sources/Nexio/Image/NexioImage.swift | 5 +- Sources/Nexio/Interceptor/Interceptor.swift | 5 +- .../Nexio/Interceptor/RetryInterceptor.swift | 5 +- .../Internal/HTTPURLResponseExtensions.swift | 5 +- .../Nexio/Internal/URLSessionExtensions.swift | 5 +- Tests/NexioTests/AuthTests.swift | 5 +- Tests/NexioTests/ImageLoaderTests.swift | 5 +- Tests/NexioTests/MockURLProtocol.swift | 5 +- Tests/NexioTests/NexioClientTests.swift | 5 +- Tests/NexioTests/RetryTests.swift | 5 +- 25 files changed, 238 insertions(+), 39 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 324e587..04972a4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,27 +1,26 @@ --- name: Bug report -about: Something isn't working as expected +about: Report a reproducible bug labels: bug --- +**Nexio version:** + +**Platform / Swift version:** + **Describe the bug** A clear description of what the bug is. -**To Reproduce** -Minimal code to reproduce the issue. - +**To reproduce** ```swift -// your code here +// Minimal code that reproduces the issue ``` **Expected behaviour** What you expected to happen. **Actual behaviour** -What actually happened, including any error messages or stack traces. +What actually happened. Include the full error message or stack trace if applicable. -**Environment** -- Nexio version: -- Swift version: -- Xcode version: -- Platform (iOS/macOS/watchOS/tvOS) and OS version: +**Additional context** +Anything else that might help (network conditions, specific server behaviour, etc.) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index baafd6d..cbaa12f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,21 +1,19 @@ --- name: Feature request -about: Suggest an improvement or new capability +about: Propose a new feature or API change labels: enhancement --- -**Problem** -Describe the problem this feature would solve. - -**Proposed solution** -How you'd like the feature to work, ideally with a code example. +**Problem / motivation** +What problem does this solve? What use case does it unlock? +**Proposed API** ```swift -// example API +// How would you use this feature? ``` **Alternatives considered** -Any alternative approaches you've considered. +Any workarounds or alternative designs you've considered. **Additional context** -Any other context or screenshots. +Links, prior art, related issues. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7caa7bd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## What changed + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Documentation / tests only + +## Checklist + +- [ ] `swift build -c release` passes with zero warnings +- [ ] `swift test` passes +- [ ] New public API has a doc comment +- [ ] `CHANGELOG.md` updated under `[Unreleased]` diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..887fb68 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to Nexio will be documented in this file. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +Nexio uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +## [1.0.0] - 2026-06-07 + +### Added +- `NexioClient` actor — type-safe GET, POST, PUT, PATCH, DELETE with automatic JSON encoding/decoding +- `NexioConfig` — base URL, timeout, default headers, retry policy, log level +- `Endpoint` protocol — type-safe request descriptors with per-request auth override +- `AuthStrategy` — bearer token, API key, custom headers, or `.none` to skip auth +- `AuthInterceptor` — dynamic auth provider (async closure, for OAuth token refresh) +- `Interceptor` protocol — adapt requests and retry failures with full pipeline control +- `RetryInterceptor` + `RetryPolicy` — none, linear, or exponential backoff; standard preset +- `NexioConfig.retry` auto-installs `RetryInterceptor` on `configure(_:)` +- `NexioConfig.protocolClasses` — inject `URLProtocol` subclasses for testing +- `ImageLoader` actor — `URLCache`-backed remote image loading with prefetch and cache clear +- `NexioImage` SwiftUI view — drop-in `AsyncImage` replacement with caching, placeholder, failure view, transition +- `NexioError` — typed errors: `invalidURL`, `noInternet`, `timeout`, `unauthorized`, `notFound`, `serverError`, `decodingFailed`, `unknown` +- Top-level `nexioGet` / `nexioPost` convenience functions over `NexioClient.shared` +- Swift 6 strict concurrency compliance — zero data-race warnings +- MIT license, license headers on all source files diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c524c6e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,26 @@ +# Code of Conduct + +## Our Pledge + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +**Positive behaviour:** +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community + +**Unacceptable behaviour:** +- Harassment, insults, or derogatory comments +- Publishing others' private information without permission +- Any conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement + +Instances of unacceptable behaviour may be reported by opening a GitHub issue or contacting the maintainer directly. All reports will be reviewed and investigated promptly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a92a005 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing to Nexio + +Thank you for your interest in contributing. This document explains how to get started. + +## Code of Conduct + +Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md). + +## Getting Started + +1. Fork the repository and clone your fork +2. Open `Package.swift` in Xcode or your preferred editor +3. Run the test suite to confirm everything passes before making changes + +```bash +swift test +``` + +## Making Changes + +- **Bugs** — open an issue first to confirm the behaviour is unintended +- **Features** — open an issue to discuss before spending time on an implementation +- **Docs / tests** — PRs welcome without prior issue + +### Branch naming + +``` +fix/short-description +feat/short-description +docs/short-description +``` + +### Code style + +- Swift 6 strict concurrency must stay clean — no `@unchecked Sendable` without a justifying comment +- No new external dependencies +- Public API changes require a doc comment update and a new test +- Run `swift build -c release` and `swift test` before opening a PR + +## Pull Request Checklist + +- [ ] `swift build -c release` passes with zero errors and zero warnings +- [ ] `swift test` passes (all platforms you can test) +- [ ] New public API has a doc comment +- [ ] `CHANGELOG.md` updated under `[Unreleased]` +- [ ] PR description explains *what* changed and *why* + +## Reporting Issues + +Use GitHub Issues. For security vulnerabilities, see [SECURITY.md](SECURITY.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..3ac66d1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +|---------|-----------| +| 1.x | ✅ | + +## Reporting a Vulnerability + +**Do not open a public GitHub issue for security vulnerabilities.** + +Please report security issues by emailing the maintainer directly (see GitHub profile) or by using [GitHub's private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability). + +Include: +- A description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (optional) + +You will receive a response within 72 hours. Once confirmed, a fix will be released and you will be credited in the changelog (unless you prefer otherwise). diff --git a/Sources/Nexio/Auth/AuthInterceptor.swift b/Sources/Nexio/Auth/AuthInterceptor.swift index 0065dda..4a3ce3b 100644 --- a/Sources/Nexio/Auth/AuthInterceptor.swift +++ b/Sources/Nexio/Auth/AuthInterceptor.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation /// An ``Interceptor`` that injects authentication headers from a dynamic provider. @@ -47,4 +50,4 @@ public struct AuthInterceptor: Interceptor { ) async -> Bool { false // Auth interceptor does not handle retries } -} +} \ No newline at end of file diff --git a/Sources/Nexio/Auth/AuthStrategy.swift b/Sources/Nexio/Auth/AuthStrategy.swift index cac470b..902f87e 100644 --- a/Sources/Nexio/Auth/AuthStrategy.swift +++ b/Sources/Nexio/Auth/AuthStrategy.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + /// Authentication strategy applied to outgoing requests. /// /// Pass to ``NexioClient/setAuth(_:)`` for global auth, or set @@ -23,4 +26,4 @@ public enum AuthStrategy: Sendable { /// Skips authentication entirely for this request. case none -} +} \ No newline at end of file diff --git a/Sources/Nexio/Core/Endpoint.swift b/Sources/Nexio/Core/Endpoint.swift index 62d0c6c..010ca6b 100644 --- a/Sources/Nexio/Core/Endpoint.swift +++ b/Sources/Nexio/Core/Endpoint.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation /// HTTP methods supported by ``NexioClient``. @@ -58,4 +61,4 @@ public extension Endpoint { var queryItems: [URLQueryItem] { [] } var body: (any Encodable)? { nil } var auth: AuthStrategy? { nil } -} +} \ No newline at end of file diff --git a/Sources/Nexio/Core/NexioClient.swift b/Sources/Nexio/Core/NexioClient.swift index dbff32d..f567a68 100644 --- a/Sources/Nexio/Core/NexioClient.swift +++ b/Sources/Nexio/Core/NexioClient.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation // MARK: - NexioClient @@ -425,4 +428,4 @@ public func nexioPost( body: some Encodable & Sendable ) async throws -> T { try await NexioClient.shared.post(url, body: body) -} +} \ No newline at end of file diff --git a/Sources/Nexio/Core/NexioConfig.swift b/Sources/Nexio/Core/NexioConfig.swift index 46a5899..bc055f1 100644 --- a/Sources/Nexio/Core/NexioConfig.swift +++ b/Sources/Nexio/Core/NexioConfig.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation /// Configuration for ``NexioClient``. @@ -51,10 +54,15 @@ public struct NexioConfig: Sendable { /// Custom `URLProtocol` classes layered onto the session ahead of the /// system defaults — primarily for intercepting traffic in tests (e.g. /// stubbing responses with a `URLProtocol` subclass). `nil` by default. - public var protocolClasses: [AnyClass]? + /// + /// - Note: `AnyClass` is not `Sendable`; this property is intentionally + /// exempt because protocol classes are always `NSObject` subclasses + /// whose class objects are global singletons — safe to share across + /// concurrency boundaries as long as callers pass only class literals. + public nonisolated(unsafe) var protocolClasses: [AnyClass]? // MARK: - Init /// Creates a default configuration. public init() {} -} +} \ No newline at end of file diff --git a/Sources/Nexio/Core/NexioRequest.swift b/Sources/Nexio/Core/NexioRequest.swift index 1bef716..9f5b204 100644 --- a/Sources/Nexio/Core/NexioRequest.swift +++ b/Sources/Nexio/Core/NexioRequest.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation /// A handle to an in-flight Nexio request. @@ -34,4 +37,4 @@ private final class _TaskBox: @unchecked Sendable { private let wrapped: URLSessionTask init(_ task: URLSessionTask) { wrapped = task } func cancel() { wrapped.cancel() } -} +} \ No newline at end of file diff --git a/Sources/Nexio/Errors/NexioError.swift b/Sources/Nexio/Errors/NexioError.swift index 99fae1c..1adc02d 100644 --- a/Sources/Nexio/Errors/NexioError.swift +++ b/Sources/Nexio/Errors/NexioError.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation /// Typed errors surfaced by all Nexio operations. @@ -52,4 +55,4 @@ public enum NexioError: Error, LocalizedError, Sendable { return "Unknown error: \(err.localizedDescription)" } } -} +} \ No newline at end of file diff --git a/Sources/Nexio/Image/ImageLoader.swift b/Sources/Nexio/Image/ImageLoader.swift index 5383e7a..f4d0896 100644 --- a/Sources/Nexio/Image/ImageLoader.swift +++ b/Sources/Nexio/Image/ImageLoader.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation #if canImport(UIKit) @@ -110,4 +113,4 @@ public actor ImageLoader { private enum ImageDecodingError: Error { case invalidImageData -} +} \ No newline at end of file diff --git a/Sources/Nexio/Image/NexioImage.swift b/Sources/Nexio/Image/NexioImage.swift index c00397a..2c043f1 100644 --- a/Sources/Nexio/Image/NexioImage.swift +++ b/Sources/Nexio/Image/NexioImage.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + #if canImport(SwiftUI) import SwiftUI @@ -156,4 +159,4 @@ private extension Image { } } -#endif // canImport(SwiftUI) +#endif // canImport(SwiftUI) \ No newline at end of file diff --git a/Sources/Nexio/Interceptor/Interceptor.swift b/Sources/Nexio/Interceptor/Interceptor.swift index 216e133..d905394 100644 --- a/Sources/Nexio/Interceptor/Interceptor.swift +++ b/Sources/Nexio/Interceptor/Interceptor.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation /// A hook that can inspect and mutate requests before they are sent, @@ -41,4 +44,4 @@ public protocol Interceptor: Sendable { /// - attempt: Zero-based retry counter (0 = first failure). /// - Returns: `true` to retry, `false` to propagate the error. func retry(_ request: URLRequest, dueTo error: NexioError, attempt: Int) async -> Bool -} +} \ No newline at end of file diff --git a/Sources/Nexio/Interceptor/RetryInterceptor.swift b/Sources/Nexio/Interceptor/RetryInterceptor.swift index 6826e10..e2eeb92 100644 --- a/Sources/Nexio/Interceptor/RetryInterceptor.swift +++ b/Sources/Nexio/Interceptor/RetryInterceptor.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation // MARK: - RetryPolicy @@ -115,4 +118,4 @@ public struct RetryInterceptor: Interceptor { return false } } -} +} \ No newline at end of file diff --git a/Sources/Nexio/Internal/HTTPURLResponseExtensions.swift b/Sources/Nexio/Internal/HTTPURLResponseExtensions.swift index 76ebfb9..27a6f8e 100644 --- a/Sources/Nexio/Internal/HTTPURLResponseExtensions.swift +++ b/Sources/Nexio/Internal/HTTPURLResponseExtensions.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation extension HTTPURLResponse { @@ -21,4 +24,4 @@ extension HTTPURLResponse { return .serverError(statusCode: statusCode, data: data) } } -} +} \ No newline at end of file diff --git a/Sources/Nexio/Internal/URLSessionExtensions.swift b/Sources/Nexio/Internal/URLSessionExtensions.swift index 192ea8f..20605ca 100644 --- a/Sources/Nexio/Internal/URLSessionExtensions.swift +++ b/Sources/Nexio/Internal/URLSessionExtensions.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation extension URLSession { @@ -30,4 +33,4 @@ extension URLSession { return (data, http) } -} +} \ No newline at end of file diff --git a/Tests/NexioTests/AuthTests.swift b/Tests/NexioTests/AuthTests.swift index 371efcb..e31271b 100644 --- a/Tests/NexioTests/AuthTests.swift +++ b/Tests/NexioTests/AuthTests.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Testing import Foundation @testable import Nexio @@ -122,4 +125,4 @@ private struct CapturingInterceptor: Interceptor { func retry(_ request: URLRequest, dueTo error: NexioError, attempt: Int) async -> Bool { false } -} +} \ No newline at end of file diff --git a/Tests/NexioTests/ImageLoaderTests.swift b/Tests/NexioTests/ImageLoaderTests.swift index 8cc47b7..ede3a99 100644 --- a/Tests/NexioTests/ImageLoaderTests.swift +++ b/Tests/NexioTests/ImageLoaderTests.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Testing import Foundation @testable import Nexio @@ -108,4 +111,4 @@ private func makePNGData() -> Data? { #else return nil #endif -} +} \ No newline at end of file diff --git a/Tests/NexioTests/MockURLProtocol.swift b/Tests/NexioTests/MockURLProtocol.swift index 48984a5..e0ea9e2 100644 --- a/Tests/NexioTests/MockURLProtocol.swift +++ b/Tests/NexioTests/MockURLProtocol.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Foundation // MARK: - MockURLProtocol @@ -146,4 +149,4 @@ extension URLSession { config.protocolClasses = [MockURLProtocol.self] return URLSession(configuration: config) } -} +} \ No newline at end of file diff --git a/Tests/NexioTests/NexioClientTests.swift b/Tests/NexioTests/NexioClientTests.swift index 5591b49..1cd90d6 100644 --- a/Tests/NexioTests/NexioClientTests.swift +++ b/Tests/NexioTests/NexioClientTests.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Testing import Foundation @testable import Nexio @@ -213,4 +216,4 @@ private func makeConfig() -> NexioConfig { // onto the session it builds. config.protocolClasses = [MockURLProtocol.self] return config -} +} \ No newline at end of file diff --git a/Tests/NexioTests/RetryTests.swift b/Tests/NexioTests/RetryTests.swift index 3afe7b6..cfe05c4 100644 --- a/Tests/NexioTests/RetryTests.swift +++ b/Tests/NexioTests/RetryTests.swift @@ -1,3 +1,6 @@ +// Copyright (c) 2026 ANSCoder +// Licensed under the MIT License. See LICENSE in the project root for details. + import Testing import Foundation @testable import Nexio @@ -113,4 +116,4 @@ extension HTTPURLResponse { static func test401() -> HTTPURLResponse { HTTPURLResponse(url: URL(string: "https://test")!, statusCode: 401, httpVersion: nil, headerFields: nil)! } -} +} \ No newline at end of file From 582ff4d6d0861e8bcff63cef7e7b33438d3c2bd0 Mon Sep 17 00:00:00 2001 From: Anand Date: Sun, 7 Jun 2026 10:12:04 +0530 Subject: [PATCH 2/2] Fix missing EOF newline in NexioConfig.swift --- Sources/Nexio/Core/NexioConfig.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Nexio/Core/NexioConfig.swift b/Sources/Nexio/Core/NexioConfig.swift index bc055f1..4baa6eb 100644 --- a/Sources/Nexio/Core/NexioConfig.swift +++ b/Sources/Nexio/Core/NexioConfig.swift @@ -65,4 +65,4 @@ public struct NexioConfig: Sendable { /// Creates a default configuration. public init() {} -} \ No newline at end of file +}